Skip to main content

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}