| 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 | |
| 14 | use rustc_hash::FxHashSet; |
| 15 | |
| 16 | use react_compiler_hir::environment::Environment; |
| 17 | use react_compiler_hir::{ |
| 18 | FunctionId, HirFunction, IdentifierId, InstructionValue, NonLocalBinding, |
| 19 | }; |
| 20 | use 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`. |
| 25 | pub 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 | } |