react_compiler_optimization/outline_functions.rs
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//! Port of OutlineFunctions from TypeScript (`Optimization/OutlineFunctions.ts`).
7//!
8//! Extracts anonymous function expressions that do not close over any local
9//! variables into top-level outlined functions. The original instruction is
10//! replaced with a `LoadGlobal` referencing the outlined function's generated name.
11//!
12//! Conditional on `env.config.enable_function_outlining`.
13
14use rustc_hash::FxHashSet;
15
16use react_compiler_hir::environment::Environment;
17use react_compiler_hir::{
18 FunctionId, HirFunction, IdentifierId, InstructionValue, NonLocalBinding,
19};
20use react_compiler_ssa::enter_ssa::placeholder_function;
21
22/// Outline anonymous function expressions that have no captured context variables.
23///
24/// Ported from TS `outlineFunctions` in `Optimization/OutlineFunctions.ts`.
25pub fn outline_functions(
26 func: &mut HirFunction,
27 env: &mut Environment,
28 fbt_operands: &FxHashSet<IdentifierId>,
29) {
30 // Collect per-instruction actions to maintain depth-first name allocation order.
31 // Each entry: (instr index, function_id to recurse into, should_outline)
32 enum Action {
33 /// Recurse into an inner function (FunctionExpression or ObjectMethod)
34 Recurse(FunctionId),
35 /// Recurse then outline a FunctionExpression
36 RecurseAndOutline {
37 instr_idx: usize,
38 function_id: FunctionId,
39 },
40 }
41
42 let mut actions: Vec<Action> = Vec::new();
43
44 for block in func.body.blocks.values() {
45 for &instr_id in &block.instructions {
46 let instr = &func.instructions[instr_id.0 as usize];
47 let lvalue_id = instr.lvalue.identifier;
48
49 match &instr.value {
50 InstructionValue::FunctionExpression { lowered_func, .. } => {
51 let inner_func = &env.functions[lowered_func.func.0 as usize];
52
53 // Check outlining conditions (TS only checks func.id === null, not name):
54 // 1. No captured context variables
55 // 2. Anonymous (no explicit id on the inner function)
56 // 3. Not an fbt operand
57 if inner_func.context.is_empty()
58 && inner_func.id.is_none()
59 && !fbt_operands.contains(&lvalue_id)
60 {
61 actions.push(Action::RecurseAndOutline {
62 instr_idx: instr_id.0 as usize,
63 function_id: lowered_func.func,
64 });
65 } else {
66 actions.push(Action::Recurse(lowered_func.func));
67 }
68 }
69 InstructionValue::ObjectMethod { lowered_func, .. } => {
70 // Recurse into object methods (but don't outline them)
71 actions.push(Action::Recurse(lowered_func.func));
72 }
73 _ => {}
74 }
75 }
76 }
77
78 // Process actions sequentially: for each instruction, recurse first (depth-first),
79 // then generate name and outline. This matches TS ordering where inner functions
80 // get names allocated before outer ones.
81 for action in actions {
82 match action {
83 Action::Recurse(function_id) => {
84 let mut inner_func = std::mem::replace(
85 &mut env.functions[function_id.0 as usize],
86 placeholder_function(),
87 );
88 outline_functions(&mut inner_func, env, fbt_operands);
89 env.functions[function_id.0 as usize] = inner_func;
90 }
91 Action::RecurseAndOutline {
92 instr_idx,
93 function_id,
94 } => {
95 // First recurse into the inner function (depth-first)
96 let mut inner_func = std::mem::replace(
97 &mut env.functions[function_id.0 as usize],
98 placeholder_function(),
99 );
100 outline_functions(&mut inner_func, env, fbt_operands);
101 env.functions[function_id.0 as usize] = inner_func;
102
103 // Then generate the name and outline (after recursion, matching TS order)
104 let hint: Option<String> = env.functions[function_id.0 as usize]
105 .id
106 .clone()
107 .or_else(|| env.functions[function_id.0 as usize].name_hint.clone());
108 let generated_name = env.generate_globally_unique_identifier_name(hint.as_deref());
109
110 // Set the id on the inner function
111 env.functions[function_id.0 as usize].id = Some(generated_name.clone());
112
113 // Outline the function
114 let outlined_func = env.functions[function_id.0 as usize].clone();
115 env.outline_function(outlined_func, None);
116
117 // Replace the instruction value with LoadGlobal
118 let loc = func.instructions[instr_idx].value.loc().cloned();
119 func.instructions[instr_idx].value = InstructionValue::LoadGlobal {
120 binding: NonLocalBinding::Global {
121 name: generated_name,
122 },
123 loc,
124 };
125 }
126 }
127 }
128}