1// Copyright (c) Meta Platforms, Inc. and affiliates.2//3// This source code is licensed under the MIT license found in the4// LICENSE file in the root directory of this source tree.56//! PruneUnusedLValues (PruneTemporaryLValues)7//!8//! Nulls out lvalues for temporary variables that are never accessed later.9//!10//! Corresponds to `src/ReactiveScopes/PruneTemporaryLValues.ts`.1112use rustc_hash::FxHashSet;1314use react_compiler_hir::{15 DeclarationId, EvaluationOrder, Place, ReactiveFunction, ReactiveInstruction,16 ReactiveStatement, ReactiveValue, environment::Environment,17};1819use crate::visitors::{self, ReactiveFunctionVisitor};2021/// 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 ReactiveFunctionVisitor29/// takes immutable references, so we cannot modify lvalues during the visit.30/// The TS version stores mutable instruction references and modifies them31/// after the visitor completes.32pub 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);3940 // 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}4748/// 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.51type LValues = FxHashSet<DeclarationId>;5253/// TS: `class Visitor extends ReactiveFunctionVisitor<LValues>`54struct Visitor<'a> {55 env: &'a Environment,56}5758impl ReactiveFunctionVisitor for Visitor<'_> {59 type State = LValues;6061 fn env(&self) -> &Environment {62 self.env63 }6465 /// 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 }7071 /// 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}8485/// Phase 2: Walk the tree and null out lvalues whose DeclarationId is unused.86/// This is necessary because Rust's visitor takes immutable references.87fn 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}109110fn 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}123124fn 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}160161fn 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}