main
rs 237 lines 8.22 KB
Raw
1 // Copyright (c) Meta Platforms, Inc. and affiliates.
2 //
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the root directory of this source tree.
5
6 //! PruneUnusedLValues (PruneTemporaryLValues)
7 //!
8 //! Nulls out lvalues for temporary variables that are never accessed later.
9 //!
10 //! Corresponds to `src/ReactiveScopes/PruneTemporaryLValues.ts`.
11
12 use rustc_hash::FxHashSet;
13
14 use react_compiler_hir::{
15 DeclarationId, EvaluationOrder, Place, ReactiveFunction, ReactiveInstruction,
16 ReactiveStatement, ReactiveValue, environment::Environment,
17 };
18
19 use crate::visitors::{self, ReactiveFunctionVisitor};
20
21 /// Nulls out lvalues for unnamed temporaries that are never used.
22 /// TS: `pruneUnusedLValues`
23 ///
24 /// Uses ReactiveFunctionVisitor to collect unnamed lvalue DeclarationIds,
25 /// removing them when referenced as operands. After the visitor pass,
26 /// a second pass nulls out the remaining unused lvalues.
27 ///
28 /// This uses a two-phase approach because Rust's ReactiveFunctionVisitor
29 /// takes immutable references, so we cannot modify lvalues during the visit.
30 /// The TS version stores mutable instruction references and modifies them
31 /// after the visitor completes.
32 pub fn prune_unused_lvalues(func: &mut ReactiveFunction, env: &Environment) {
33 // Phase 1: Use ReactiveFunctionVisitor to identify unused unnamed lvalues.
34 // When we see an unnamed lvalue on an instruction, we add its DeclarationId.
35 // When we see a place reference (operand), we remove its DeclarationId.
36 let visitor = Visitor { env };
37 let mut lvalues: FxHashSet<DeclarationId> = FxHashSet::default();
38 visitors::visit_reactive_function(func, &visitor, &mut lvalues);
39
40 // Phase 2: Null out lvalues whose DeclarationId remains in the map.
41 // In the TS, this is done by iterating the stored instruction references.
42 // In Rust, we walk the tree to find instructions with matching DeclarationIds.
43 if !lvalues.is_empty() {
44 null_unused_lvalues(&mut func.body, env, &lvalues);
45 }
46 }
47
48 /// TS: `type LValues = Map<DeclarationId, ReactiveInstruction>`
49 /// In Rust, we only need the set of DeclarationIds (not the instruction refs)
50 /// because we apply changes in a separate pass.
51 type LValues = FxHashSet<DeclarationId>;
52
53 /// TS: `class Visitor extends ReactiveFunctionVisitor<LValues>`
54 struct Visitor<'a> {
55 env: &'a Environment,
56 }
57
58 impl ReactiveFunctionVisitor for Visitor<'_> {
59 type State = LValues;
60
61 fn env(&self) -> &Environment {
62 self.env
63 }
64
65 /// TS: `visitPlace(_id, place, state) { state.delete(place.identifier.declarationId) }`
66 fn visit_place(&self, _id: EvaluationOrder, place: &Place, state: &mut LValues) {
67 let ident = &self.env.identifiers[place.identifier.0 as usize];
68 state.remove(&ident.declaration_id);
69 }
70
71 /// TS: `visitInstruction(instruction, state)`
72 /// Calls traverseInstruction first (visits operands via visitPlace),
73 /// then checks if the lvalue is unnamed and adds to map.
74 fn visit_instruction(&self, instruction: &ReactiveInstruction, state: &mut LValues) {
75 self.traverse_instruction(instruction, state);
76 if let Some(lv) = &instruction.lvalue {
77 let ident = &self.env.identifiers[lv.identifier.0 as usize];
78 if ident.name.is_none() {
79 state.insert(ident.declaration_id);
80 }
81 }
82 }
83 }
84
85 /// Phase 2: Walk the tree and null out lvalues whose DeclarationId is unused.
86 /// This is necessary because Rust's visitor takes immutable references.
87 fn null_unused_lvalues(
88 block: &mut Vec<ReactiveStatement>,
89 env: &Environment,
90 unused: &FxHashSet<DeclarationId>,
91 ) {
92 for stmt in block.iter_mut() {
93 match stmt {
94 ReactiveStatement::Instruction(instr) => {
95 null_unused_in_instruction(instr, env, unused);
96 }
97 ReactiveStatement::Scope(scope) => {
98 null_unused_lvalues(&mut scope.instructions, env, unused);
99 }
100 ReactiveStatement::PrunedScope(scope) => {
101 null_unused_lvalues(&mut scope.instructions, env, unused);
102 }
103 ReactiveStatement::Terminal(stmt) => {
104 null_unused_in_terminal(&mut stmt.terminal, env, unused);
105 }
106 }
107 }
108 }
109
110 fn null_unused_in_instruction(
111 instr: &mut ReactiveInstruction,
112 env: &Environment,
113 unused: &FxHashSet<DeclarationId>,
114 ) {
115 if let Some(lv) = &instr.lvalue {
116 let ident = &env.identifiers[lv.identifier.0 as usize];
117 if unused.contains(&ident.declaration_id) {
118 instr.lvalue = None;
119 }
120 }
121 null_unused_in_value(&mut instr.value, env, unused);
122 }
123
124 fn null_unused_in_value(
125 value: &mut ReactiveValue,
126 env: &Environment,
127 unused: &FxHashSet<DeclarationId>,
128 ) {
129 match value {
130 ReactiveValue::SequenceExpression {
131 instructions,
132 value: inner,
133 ..
134 } => {
135 for instr in instructions.iter_mut() {
136 null_unused_in_instruction(instr, env, unused);
137 }
138 null_unused_in_value(inner, env, unused);
139 }
140 ReactiveValue::LogicalExpression { left, right, .. } => {
141 null_unused_in_value(left, env, unused);
142 null_unused_in_value(right, env, unused);
143 }
144 ReactiveValue::ConditionalExpression {
145 test,
146 consequent,
147 alternate,
148 ..
149 } => {
150 null_unused_in_value(test, env, unused);
151 null_unused_in_value(consequent, env, unused);
152 null_unused_in_value(alternate, env, unused);
153 }
154 ReactiveValue::OptionalExpression { value: inner, .. } => {
155 null_unused_in_value(inner, env, unused);
156 }
157 ReactiveValue::Instruction(_) => {}
158 }
159 }
160
161 fn null_unused_in_terminal(
162 terminal: &mut react_compiler_hir::ReactiveTerminal,
163 env: &Environment,
164 unused: &FxHashSet<DeclarationId>,
165 ) {
166 use react_compiler_hir::ReactiveTerminal;
167 match terminal {
168 ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {}
169 ReactiveTerminal::Return { .. } | ReactiveTerminal::Throw { .. } => {}
170 ReactiveTerminal::For {
171 init,
172 test,
173 update,
174 loop_block,
175 ..
176 } => {
177 null_unused_in_value(init, env, unused);
178 null_unused_in_value(test, env, unused);
179 null_unused_lvalues(loop_block, env, unused);
180 if let Some(update) = update {
181 null_unused_in_value(update, env, unused);
182 }
183 }
184 ReactiveTerminal::ForOf {
185 init,
186 test,
187 loop_block,
188 ..
189 } => {
190 null_unused_in_value(init, env, unused);
191 null_unused_in_value(test, env, unused);
192 null_unused_lvalues(loop_block, env, unused);
193 }
194 ReactiveTerminal::ForIn {
195 init, loop_block, ..
196 } => {
197 null_unused_in_value(init, env, unused);
198 null_unused_lvalues(loop_block, env, unused);
199 }
200 ReactiveTerminal::DoWhile {
201 loop_block, test, ..
202 } => {
203 null_unused_lvalues(loop_block, env, unused);
204 null_unused_in_value(test, env, unused);
205 }
206 ReactiveTerminal::While {
207 test, loop_block, ..
208 } => {
209 null_unused_in_value(test, env, unused);
210 null_unused_lvalues(loop_block, env, unused);
211 }
212 ReactiveTerminal::If {
213 consequent,
214 alternate,
215 ..
216 } => {
217 null_unused_lvalues(consequent, env, unused);
218 if let Some(alt) = alternate {
219 null_unused_lvalues(alt, env, unused);
220 }
221 }
222 ReactiveTerminal::Switch { cases, .. } => {
223 for case in cases.iter_mut() {
224 if let Some(block) = &mut case.block {
225 null_unused_lvalues(block, env, unused);
226 }
227 }
228 }
229 ReactiveTerminal::Label { block, .. } => {
230 null_unused_lvalues(block, env, unused);
231 }
232 ReactiveTerminal::Try { block, handler, .. } => {
233 null_unused_lvalues(block, env, unused);
234 null_unused_lvalues(handler, env, unused);
235 }
236 }
237 }