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 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}