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 std::collections::HashSet;
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: &HashSet<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 {
51 lowered_func, ..
52 } => {
53 let inner_func = &env.functions[lowered_func.func.0 as usize];
54
55 // Check outlining conditions (TS only checks func.id === null, not name):
56 // 1. No captured context variables
57 // 2. Anonymous (no explicit id on the inner function)
58 // 3. Not an fbt operand
59 if inner_func.context.is_empty()
60 && inner_func.id.is_none()
61 && !fbt_operands.contains(&lvalue_id)
62 {
63 actions.push(Action::RecurseAndOutline {
64 instr_idx: instr_id.0 as usize,
65 function_id: lowered_func.func,
66 });
67 } else {
68 actions.push(Action::Recurse(lowered_func.func));
69 }
70 }
71 InstructionValue::ObjectMethod { lowered_func, .. } => {
72 // Recurse into object methods (but don't outline them)
73 actions.push(Action::Recurse(lowered_func.func));
74 }
75 _ => {}
76 }
77 }
78 }
79
80 // Process actions sequentially: for each instruction, recurse first (depth-first),
81 // then generate name and outline. This matches TS ordering where inner functions
82 // get names allocated before outer ones.
83 for action in actions {
84 match action {
85 Action::Recurse(function_id) => {
86 let mut inner_func = std::mem::replace(
87 &mut env.functions[function_id.0 as usize],
88 placeholder_function(),
89 );
90 outline_functions(&mut inner_func, env, fbt_operands);
91 env.functions[function_id.0 as usize] = inner_func;
92 }
93 Action::RecurseAndOutline {
94 instr_idx,
95 function_id,
96 } => {
97 // First recurse into the inner function (depth-first)
98 let mut inner_func = std::mem::replace(
99 &mut env.functions[function_id.0 as usize],
100 placeholder_function(),
101 );
102 outline_functions(&mut inner_func, env, fbt_operands);
103 env.functions[function_id.0 as usize] = inner_func;
104
105 // Then generate the name and outline (after recursion, matching TS order)
106 let hint: Option<String> = env.functions[function_id.0 as usize]
107 .id
108 .clone()
109 .or_else(|| env.functions[function_id.0 as usize].name_hint.clone());
110 let generated_name =
111 env.generate_globally_unique_identifier_name(hint.as_deref());
112
113 // Set the id on the inner function
114 env.functions[function_id.0 as usize].id = Some(generated_name.clone());
115
116 // Outline the function
117 let outlined_func = env.functions[function_id.0 as usize].clone();
118 env.outline_function(outlined_func, None);
119
120 // Replace the instruction value with LoadGlobal
121 let loc = func.instructions[instr_idx].value.loc().cloned();
122 func.instructions[instr_idx].value = InstructionValue::LoadGlobal {
123 binding: NonLocalBinding::Global {
124 name: generated_name,
125 },
126 loc,
127 };
128 }
129 }
130 }
131}