1// Copyright (c) Meta Platforms, Inc. and affiliates.2//3// This source code is licensed under the MIT license found in the4// LICENSE file in the root directory of this source tree.56//! Port of OutlineFunctions from TypeScript (`Optimization/OutlineFunctions.ts`).7//!8//! Extracts anonymous function expressions that do not close over any local9//! variables into top-level outlined functions. The original instruction is10//! replaced with a `LoadGlobal` referencing the outlined function's generated name.11//!12//! Conditional on `env.config.enable_function_outlining`.1314use rustc_hash::FxHashSet;1516use react_compiler_hir::environment::Environment;17use react_compiler_hir::{18 FunctionId, HirFunction, IdentifierId, InstructionValue, NonLocalBinding,19};20use react_compiler_ssa::enter_ssa::placeholder_function;2122/// 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 FunctionExpression36 RecurseAndOutline {37 instr_idx: usize,38 function_id: FunctionId,39 },40 }4142 let mut actions: Vec<Action> = Vec::new();4344 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;4849 match &instr.value {50 InstructionValue::FunctionExpression { lowered_func, .. } => {51 let inner_func = &env.functions[lowered_func.func.0 as usize];5253 // Check outlining conditions (TS only checks func.id === null, not name):54 // 1. No captured context variables55 // 2. Anonymous (no explicit id on the inner function)56 // 3. Not an fbt operand57 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 }7778 // Process actions sequentially: for each instruction, recurse first (depth-first),79 // then generate name and outline. This matches TS ordering where inner functions80 // 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;102103 // Then generate the name and outline (after recursion, matching TS order)104 let hint: Option<String> = env.functions[function_id.0 as usize]105 .id106 .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());109110 // Set the id on the inner function111 env.functions[function_id.0 as usize].id = Some(generated_name.clone());112113 // Outline the function114 let outlined_func = env.functions[function_id.0 as usize].clone();115 env.outline_function(outlined_func, None);116117 // Replace the instruction value with LoadGlobal118 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}
Code quality findings 13
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let instr = &func.instructions[instr_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let inner_func = &env.functions[lowered_func.func.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
&mut env.functions[function_id.0 as usize],
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
env.functions[function_id.0 as usize] = inner_func;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
&mut env.functions[function_id.0 as usize],
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
env.functions[function_id.0 as usize] = inner_func;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let hint: Option<String> = env.functions[function_id.0 as usize]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
.or_else(|| env.functions[function_id.0 as usize].name_hint.clone());
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
env.functions[function_id.0 as usize].id = Some(generated_name.clone());
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let outlined_func = env.functions[function_id.0 as usize].clone();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let loc = func.instructions[instr_idx].value.loc().cloned();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
func.instructions[instr_idx].value = InstructionValue::LoadGlobal {
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
actions.push(Action::Recurse(lowered_func.func));