main
rs 219 lines 8.17 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 //! Recursively analyzes nested function expressions and object methods to infer
7 //! their aliasing effect signatures.
8 //!
9 //! Ported from TypeScript `src/Inference/AnalyseFunctions.ts`.
10 //!
11 //! Runs inferMutationAliasingEffects, deadCodeElimination,
12 //! inferMutationAliasingRanges, rewriteInstructionKindsBasedOnReassignment,
13 //! and inferReactiveScopeVariables on each inner function.
14
15 use indexmap::IndexMap;
16 use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory};
17 use react_compiler_hir::environment::Environment;
18 use rustc_hash::FxHashSet;
19
20 use react_compiler_hir::{
21 AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HIR, HirFunction, IdentifierId,
22 InstructionValue, Place, ReactFunctionType,
23 };
24
25 /// Analyse all nested function expressions and object methods in `func`.
26 ///
27 /// For each inner function found, runs `lower_with_mutation_aliasing` to infer
28 /// its aliasing effects, then resets context variable mutable ranges.
29 ///
30 /// The optional `debug_logger` callback is invoked after processing each inner
31 /// function, receiving `(&HirFunction, &Environment)` so the caller can produce
32 /// debug output. This mirrors the TS `fn.env.logger?.debugLogIRs` call inside
33 /// `lowerWithMutationAliasing`.
34 ///
35 /// Corresponds to TS `analyseFunctions(func: HIRFunction): void`.
36 pub fn analyse_functions<F>(
37 func: &mut HirFunction,
38 env: &mut Environment,
39 debug_logger: &mut F,
40 ) -> Result<(), CompilerDiagnostic>
41 where
42 F: FnMut(&HirFunction, &Environment),
43 {
44 // Collect FunctionIds from FunctionExpression/ObjectMethod instructions.
45 // We collect first to avoid borrow conflicts with env.functions.
46 let mut inner_func_ids: Vec<FunctionId> = Vec::new();
47 for (_block_id, block) in &func.body.blocks {
48 for instr_id in &block.instructions {
49 let instr = &func.instructions[instr_id.0 as usize];
50 match &instr.value {
51 InstructionValue::FunctionExpression { lowered_func, .. }
52 | InstructionValue::ObjectMethod { lowered_func, .. } => {
53 inner_func_ids.push(lowered_func.func);
54 }
55 _ => {}
56 }
57 }
58 }
59
60 // Process each inner function
61 for func_id in inner_func_ids {
62 // Take the inner function out of the arena to avoid borrow conflicts
63 let mut inner_func = std::mem::replace(
64 &mut env.functions[func_id.0 as usize],
65 placeholder_function(),
66 );
67
68 lower_with_mutation_aliasing(&mut inner_func, env, debug_logger)?;
69
70 // If an invariant error was recorded, put the function back and stop processing
71 if env.has_invariant_errors() {
72 env.functions[func_id.0 as usize] = inner_func;
73 return Ok(());
74 }
75
76 // Reset mutable range for outer inferMutationAliasingEffects.
77 //
78 // NOTE: inferReactiveScopeVariables makes identifiers in the scope
79 // point to the *same* mutableRange instance (in TS). In Rust, scopes
80 // are stored in an arena, so we reset both the identifier's range
81 // and clear its scope.
82 for operand in &inner_func.context {
83 let new_range = env.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));
84 let ident = &mut env.identifiers[operand.identifier.0 as usize];
85 ident.mutable_range = new_range;
86 ident.scope = None;
87 }
88
89 // Put the function back
90 env.functions[func_id.0 as usize] = inner_func;
91 }
92
93 Ok(())
94 }
95
96 /// Run mutation/aliasing inference on an inner function.
97 ///
98 /// Corresponds to TS `lowerWithMutationAliasing(fn: HIRFunction): void`.
99 fn lower_with_mutation_aliasing<F>(
100 func: &mut HirFunction,
101 env: &mut Environment,
102 debug_logger: &mut F,
103 ) -> Result<(), CompilerDiagnostic>
104 where
105 F: FnMut(&HirFunction, &Environment),
106 {
107 // Phase 1: Recursively analyse nested functions first (depth-first)
108 analyse_functions(func, env, debug_logger)?;
109
110 // inferMutationAliasingEffects on the inner function
111 crate::infer_mutation_aliasing_effects::infer_mutation_aliasing_effects(func, env, true)?;
112
113 // Check for invariant errors (e.g., uninitialized value kind)
114 // In TS, these throw from within inferMutationAliasingEffects, aborting
115 // the rest of the function processing.
116 if env.has_invariant_errors() {
117 return Ok(());
118 }
119
120 // deadCodeElimination for inner functions
121 react_compiler_optimization::dead_code_elimination(func, env);
122
123 // inferMutationAliasingRanges — returns the externally-visible function effects
124 let function_effects =
125 crate::infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges(func, env, true)?;
126
127 // rewriteInstructionKindsBasedOnReassignment
128 if let Err(err) = react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(func, env)
129 {
130 env.errors.merge(err);
131 return Ok(());
132 }
133
134 // inferReactiveScopeVariables on the inner function
135 crate::infer_reactive_scope_variables::infer_reactive_scope_variables(func, env)?;
136
137 func.aliasing_effects = Some(function_effects.clone());
138
139 // Phase 2: Populate the Effect of each context variable to use in inferring
140 // the outer function. Corresponds to TS Phase 2 in lowerWithMutationAliasing.
141 let mut captured_or_mutated: FxHashSet<IdentifierId> = FxHashSet::default();
142 for effect in &function_effects {
143 match effect {
144 AliasingEffect::Assign { from, .. }
145 | AliasingEffect::Alias { from, .. }
146 | AliasingEffect::Capture { from, .. }
147 | AliasingEffect::CreateFrom { from, .. }
148 | AliasingEffect::MaybeAlias { from, .. } => {
149 captured_or_mutated.insert(from.identifier);
150 }
151 AliasingEffect::Mutate { value, .. }
152 | AliasingEffect::MutateConditionally { value }
153 | AliasingEffect::MutateTransitive { value }
154 | AliasingEffect::MutateTransitiveConditionally { value } => {
155 captured_or_mutated.insert(value.identifier);
156 }
157 AliasingEffect::Impure { .. }
158 | AliasingEffect::Render { .. }
159 | AliasingEffect::MutateFrozen { .. }
160 | AliasingEffect::MutateGlobal { .. }
161 | AliasingEffect::CreateFunction { .. }
162 | AliasingEffect::Create { .. }
163 | AliasingEffect::Freeze { .. }
164 | AliasingEffect::ImmutableCapture { .. } => {
165 // no-op
166 }
167 AliasingEffect::Apply { .. } => {
168 return Err(CompilerDiagnostic::new(
169 ErrorCategory::Invariant,
170 "[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects",
171 None,
172 ));
173 }
174 }
175 }
176
177 for operand in &mut func.context {
178 if captured_or_mutated.contains(&operand.identifier) || operand.effect == Effect::Capture {
179 operand.effect = Effect::Capture;
180 } else {
181 operand.effect = Effect::Read;
182 }
183 }
184
185 // Log the inner function's state (mirrors TS: fn.env.logger?.debugLogIRs)
186 debug_logger(func, env);
187
188 Ok(())
189 }
190
191 /// Create a placeholder HirFunction for temporarily swapping an inner function
192 /// out of `env.functions` via `std::mem::replace`. The placeholder is never
193 /// read — the real function is swapped back immediately after processing.
194 fn placeholder_function() -> HirFunction {
195 HirFunction {
196 loc: None,
197 id: None,
198 name_hint: None,
199 fn_type: ReactFunctionType::Other,
200 params: Vec::new(),
201 return_type_annotation: None,
202 returns: Place {
203 identifier: IdentifierId(0),
204 effect: Effect::Unknown,
205 reactive: false,
206 loc: None,
207 },
208 context: Vec::new(),
209 body: HIR {
210 entry: BlockId(0),
211 blocks: IndexMap::default(),
212 },
213 instructions: Vec::new(),
214 generator: false,
215 is_async: false,
216 directives: Vec::new(),
217 aliasing_effects: None,
218 }
219 }