WP4: Function analysis & effects (@analyzer subagent)

/home/jm/data/ai/cloudlang-trees/react · 7 messages · 41 parts · · 73,863 tokens
Created Feb 10, 05:01:37 AM · Updated Feb 10, 05:09:06 AM · 1 syncs
userFeb 10, 05:01:37 AM

You are analyzing the React Compiler's function analysis and effect inference passes. This is RESEARCH ONLY -- do NOT edit or write any files. Return your complete analysis as your final message.

The React Compiler source is at: /home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/

Files to read and analyze:

  1. Inference/AnalyseFunctions.ts - Recursive nested function analysis (THE key pass)
  2. Inference/InferReactivePlaces.ts - Reactivity inference
  3. Inference/InferEffectDependencies.ts - Effect dependency inference
  4. Inference/DropManualMemoization.ts - Drops useMemo/useCallback
  5. Inference/InlineImmediatelyInvokedFunctionExpressions.ts - IIFE inlining
  6. Inference/ControlDominators.ts - Control flow dominator analysis

Also read CloudLang's current scope analysis for comparison:

  • /home/jm/data/ai/cloudlang-trees/react/lib/scope.ml
  • /home/jm/data/ai/cloudlang-trees/react/lib/scope.mli

Produce documentation for each pass using this template:

# Pass Name

## File
## Purpose
## Input Invariants
## Output Guarantees
## Algorithm (detailed, step-by-step)
## Key Data Structures
## Edge Cases
## Mapping to CloudLang
## Example (before/after)

Specific questions to answer:

  1. AnalyseFunctions (most important):

    • Walk through the complete algorithm step by step
    • How does recursive descent into nested functions work?
    • What mini-pipeline runs on each inner function?
    • How does it classify context variables as Capture vs Read?
    • What effects indicate capture? (Capture, Alias, Assign, MaybeAlias, CreateFrom → captured; Mutate, MutateTransitive → captured; ImmutableCapture, Freeze, Create, Impure, Render → not)
    • How does it reset mutable ranges on context variables?
    • What's the Apply effects assertion?
    • How does this relate to CloudLang's analyze_closure which collects free_vars, captured_mutable, called_functions?
  2. InferReactivePlaces: How does it determine which values are "reactive"? The stability tracking mechanism. The general pattern of "propagating a property through data flow."

  3. InferEffectDependencies: How does it infer useEffect dependencies? The general pattern of "what does this function depend on."

  4. IIFE Inlining: How does it detect and inline IIFEs?

  5. ControlDominators: What dominator analysis is used? How does it support inference?

  6. For CloudLang specifically:

    • CloudLang's scope.ml does a simpler version: finds free variables, checks if mutable state. How would we upgrade?
    • React classifies context variables as Capture vs Read. CloudLang classifies as captured_mutable vs free_vars. What's the delta?
    • Sketch an OCaml module signature for an upgraded function analysis pass
    • In CloudLang, inner functions become separate Workers. The effect signature determines what bindings (KV, service, D1) each needs. How does AnalyseFunctions' output map to binding inference?

Return your COMPLETE analysis in your final message. Do NOT write any files.

assistantFeb 10, 05:01:37 AM
glm-4.7 · zai-coding-plan
This is a research-only task. I need to analyze the React Compiler's function analysis and effect inference passes. Let me read all the files mentioned and produce comprehensive documentation for each pass. Let me start by reading the files: 1. First, the React Compiler files 2. Then the CloudLang scope analysis files for comparison I'll read them all and then produce the complete analysis.

I'll analyze the React Compiler's function analysis and effect inference passes, then compare with CloudLang's scope analysis. Let me read all the relevant files.

readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {CompilerError} from '../CompilerError';
00009| import {Effect, HIRFunction, IdentifierId, makeInstructionId} from '../HIR';
00010| import {deadCodeElimination} from '../Optimization';
00011| import {inferReactiveScopeVariables} from '../ReactiveScopes';
00012| import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
00013| import {assertExhaustive} from '../Utils/utils';
00014| import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
00015| import {inferMutationAliasingRanges} from './InferMutationAliasingRanges';
00016| 
00017| export default function analyseFunctions(func: HIRFunction): void {
00018|   for (const [_, block] of func.body.blocks) {
00019|     for (const instr of block.instructions) {
00020|       switch (instr.value.kind) {
00021|         case 'ObjectMethod':
00022|         case 'FunctionExpression': {
00023|           lowerWithMutationAliasing(instr.value.loweredFunc.func);
00024| 
00025|           /**
00026|            * Reset mutable range for outer inferReferenceEffects
00027|            */
00028|           for (const operand of instr.value.loweredFunc.func.context) {
00029|             /**
00030|              * NOTE: inferReactiveScopeVariables makes identifiers in the scope
00031|              * point to the *same* mutableRange instance. Resetting start/end
00032|              * here is insufficient, because a later mutation of the range
00033|              * for any one identifier could affect the range for other identifiers.
00034|              */
00035|             operand.identifier.mutableRange = {
00036|               start: makeInstructionId(0),
00037|               end: makeInstructionId(0),
00038|             };
00039|             operand.identifi...
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {CompilerError} from '..';
00009| import {
00010|   Effect,
00011|   Environment,
00012|   HIRFunction,
00013|   Identifier,
00014|   IdentifierId,
00015|   Instruction,
00016|   Place,
00017|   evaluatesToStableTypeOrContainer,
00018|   getHookKind,
00019|   isStableType,
00020|   isStableTypeContainer,
00021|   isUseOperator,
00022| } from '../HIR';
00023| import {
00024|   eachInstructionLValue,
00025|   eachInstructionOperand,
00026|   eachInstructionValueOperand,
00027|   eachTerminalOperand,
00028| } from '../HIR/visitors';
00029| import {
00030|   findDisjointMutableValues,
00031|   isMutable,
00032| } from '../ReactiveScopes/InferReactiveScopeVariables';
00033| import DisjointSet from '../Utils/DisjointSet';
00034| import {assertExhaustive} from '../Utils/utils';
00035| import {createControlDominators} from './ControlDominators';
00036| 
00037| /**
00038|  * Side map to track and propagate sources of stability (i.e. hook calls such as
00039|  * `useRef()` and property reads such as `useState()[1]). Note that this
00040|  * requires forward data flow analysis since stability is not part of React
00041|  * Compiler's type system.
00042|  */
00043| class StableSidemap {
00044|   map: Map<IdentifierId, {isStable: boolean}> = new Map();
00045|   env: Environment;
00046| 
00047|   constructor(env: Environment) {
00048|     this.env = env;
00049|   }
00050| 
00051|   handleInstruction(instr: Instruction): void {
00052|     const {value, lvalue} = instr;
00053| 
00054|     switch (value.kind) {
00055|       case 'CallExpression':
00056|       case 'MethodCall': {
00057|         /**
00058|          * Sources of stability are known hook calls
00059|          */
00060|         if (evaluatesToStableTypeOrContaine...
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import * as t from '@babel/types';
00009| import {CompilerError, SourceLocation} from '..';
00010| import {
00011|   ArrayExpression,
00012|   Effect,
00013|   FunctionExpression,
00014|   GeneratedSource,
00015|   HIRFunction,
00016|   IdentifierId,
00017|   Instruction,
00018|   makeInstructionId,
00019|   TInstruction,
00020|   InstructionId,
00021|   ScopeId,
00022|   ReactiveScopeDependency,
00023|   Place,
00024|   ReactiveScope,
00025|   ReactiveScopeDependencies,
00026|   Terminal,
00027|   isUseRefType,
00028|   isSetStateType,
00029|   isFireFunctionType,
00030|   makeScopeId,
00031|   HIR,
00032|   BasicBlock,
00033|   BlockId,
00034|   isEffectEventFunctionType,
00035| } from '../HIR';
00036| import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads';
00037| import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies';
00038| import {ReactiveScopeDependencyTreeHIR} from '../HIR/DeriveMinimalDependenciesHIR';
00039| import {DEFAULT_EXPORT} from '../HIR/Environment';
00040| import {
00041|   createTemporaryPlace,
00042|   fixScopeAndIdentifierRanges,
00043|   markInstructionIds,
00044|   markPredecessors,
00045|   reversePostorderBlocks,
00046| } from '../HIR/HIRBuilder';
00047| import {
00048|   collectTemporariesSidemap,
00049|   DependencyCollectionContext,
00050|   handleInstruction,
00051| } from '../HIR/PropagateScopeDependenciesHIR';
00052| import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils';
00053| import {
00054|   eachInstructionOperand,
00055|   eachTerminalOperand,
00056|   terminalFallthrough,
00057| } from '../HIR/visitors';
00058| import {empty} from '../Utils/Stack';
00059| import {getOrInsertWith} from '../Utils/ut...
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {CompilerDiagnostic, CompilerError, SourceLocation} from '..';
00009| import {ErrorCategory} from '../CompilerError';
00010| import {
00011|   CallExpression,
00012|   Effect,
00013|   Environment,
00014|   FinishMemoize,
00015|   FunctionExpression,
00016|   HIRFunction,
00017|   IdentifierId,
00018|   Instruction,
00019|   InstructionId,
00020|   InstructionValue,
00021|   LoadGlobal,
00022|   LoadLocal,
00023|   ManualMemoDependency,
00024|   MethodCall,
00025|   Place,
00026|   PropertyLoad,
00027|   SpreadPattern,
00028|   StartMemoize,
00029|   TInstruction,
00030|   getHookKindForType,
00031|   makeInstructionId,
00032| } from '../HIR';
00033| import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
00034| import {Result} from '../Utils/Result';
00035| 
00036| type ManualMemoCallee = {
00037|   kind: 'useMemo' | 'useCallback';
00038|   loadInstr: TInstruction<LoadGlobal> | TInstruction<PropertyLoad>;
00039| };
00040| 
00041| type IdentifierSidemap = {
00042|   functions: Map<IdentifierId, TInstruction<FunctionExpression>>;
00043|   manualMemos: Map<IdentifierId, ManualMemoCallee>;
00044|   react: Set<IdentifierId>;
00045|   maybeDepsLists: Map<IdentifierId, {loc: SourceLocation; deps: Array<Place>}>;
00046|   maybeDeps: Map<IdentifierId, ManualMemoDependency>;
00047|   optionals: Set<IdentifierId>;
00048| };
00049| 
00050| /**
00051|  * Collect loads from named variables and property reads from @value
00052|  * into `maybeDeps`
00053|  * Returns the variable + property reads represented by @instr
00054|  */
00055| export function collectMaybeMemoDependencies(
00056|   value: InstructionValue,
00057|   maybeDeps: Map<IdentifierId, ManualMemoDependency>,
00058|   optional: boolean,
000...
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {
00009|   BasicBlock,
00010|   BlockId,
00011|   Environment,
00012|   FunctionExpression,
00013|   GeneratedSource,
00014|   GotoTerminal,
00015|   GotoVariant,
00016|   HIRFunction,
00017|   IdentifierId,
00018|   InstructionKind,
00019|   LabelTerminal,
00020|   Place,
00021|   isStatementBlockKind,
00022|   makeInstructionId,
00023|   mergeConsecutiveBlocks,
00024|   promoteTemporary,
00025|   reversePostorderBlocks,
00026| } from '../HIR';
00027| import {
00028|   createTemporaryPlace,
00029|   markInstructionIds,
00030|   markPredecessors,
00031| } from '../HIR/HIRBuilder';
00032| import {eachInstructionValueOperand} from '../HIR/visitors';
00033| import {retainWhere} from '../Utils/utils';
00034| 
00035| /*
00036|  * Inlines immediately invoked function expressions (IIFEs) to allow more fine-grained memoization
00037|  * of the values they produce.
00038|  *
00039|  * Example:
00040|  *
00041|  * ```
00042|  * const x = (() => {
00043|  *    const x = [];
00044|  *    x.push(foo());
00045|  *    return x;
00046|  * })();
00047|  *
00048|  * =>
00049|  *
00050|  * bb0:
00051|  *     // placeholder for the result, all return statements will assign here
00052|  *    let t0;
00053|  *    // Label allows using a goto (break) to exit out of the body
00054|  *    Label block=bb1 fallthrough=bb2
00055|  * bb1:
00056|  *    // code within the function expression
00057|  *    const x0 = [];
00058|  *    x0.push(foo());
00059|  *    // return is replaced by assignment to the result variable...
00060|  *    t0 = x0;
00061|  *    // ...and a goto to the code after the function expression invocation
00062|  *    Goto bb2
00063|  * bb2:
00064|  *    // code after the IIFE call
00065|  *    const x = t0;
00066|  * ``...
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/ControlDominators.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {BlockId, computePostDominatorTree, HIRFunction, Place} from '../HIR';
00009| import {PostDominator} from '../HIR/Dominator';
00010| 
00011| export type ControlDominators = (id: BlockId) => boolean;
00012| 
00013| /**
00014|  * Returns an object that lazily calculates whether particular blocks are controlled
00015|  * by values of interest. Which values matter are up to the caller.
00016|  */
00017| export function createControlDominators(
00018|   fn: HIRFunction,
00019|   isControlVariable: (place: Place) => boolean,
00020| ): ControlDominators {
00021|   const postDominators = computePostDominatorTree(fn, {
00022|     includeThrowsAsExitNode: false,
00023|   });
00024|   const postDominatorFrontierCache = new Map<BlockId, Set<BlockId>>();
00025| 
00026|   function isControlledBlock(id: BlockId): boolean {
00027|     let controlBlocks = postDominatorFrontierCache.get(id);
00028|     if (controlBlocks === undefined) {
00029|       controlBlocks = postDominatorFrontier(fn, postDominators, id);
00030|       postDominatorFrontierCache.set(id, controlBlocks);
00031|     }
00032|     for (const blockId of controlBlocks) {
00033|       const controlBlock = fn.body.blocks.get(blockId)!;
00034|       switch (controlBlock.terminal.kind) {
00035|         case 'if':
00036|         case 'branch': {
00037|           if (isControlVariable(controlBlock.terminal.test)) {
00038|             return true;
00039|           }
00040|           break;
00041|         }
00042|         case 'switch': {
00043|           if (isControlVariable(controlBlock.terminal.test)) {
00044|             return true;
00045|           }
00046|           for (const case_ of controlBlock.terminal.cases) {
00047|             if (case_.test !== null && ...
readcompleted
lib/scope.ml
Output
<file>
00001| (** Scope analysis for TypeScript ESTree AST.
00002| 
00003|     Two-pass analysis: 1. Declaration pass: collect all module-scope const/let
00004|     bindings, noting which are mutable state (new Map/Set) and which are
00005|     functions. 2. Reference pass: for each arrow function body, collect
00006|     Identifier references that are not locally declared -> these are free
00007|     variables. *)
00008| 
00009| type error = Expected_program_node
00010| 
00011| let error_to_string = function
00012|   | Expected_program_node -> "expected Program node"
00013| 
00014| type var_info = { name : string; is_mutable_state : bool; is_function : bool }
00015| 
00016| type closure_info = {
00017|   name : string;
00018|   free_vars : string list;
00019|   called_functions : string list;
00020|   captures_mutable : string list;
00021| }
00022| 
00023| type analysis_result = {
00024|   closures : closure_info list;
00025|   module_vars : var_info list;
00026|   exports : string list;
00027| }
00028| 
00029| (** Check whether an init expression is [new Map(...)], [new Set(...)], etc. *)
00030| let is_mutable_init = function
00031|   | Ast.NewExpression { callee = Ast.Identifier { name }; _ }
00032|     when name = "Map" || name = "Set" || name = "Array" ->
00033|       true
00034|   | _ -> false
00035| 
00036| (** Check whether an init expression is an arrow function. *)
00037| let is_arrow_init = function
00038|   | Ast.ArrowFunctionExpression _ -> true
00039|   | _ -> false
00040| 
00041| let option_exists predicate = function
00042|   | Some value -> predicate value
00043|   | None -> false
00044| 
00045| (** Extract parameter names from an arrow function's param list. *)
00046| let param_names params =
00047|   List.filter_map
00048|     (function Ast.Identifier { name } -> Some name | _ -> None)
00049|     params
00050| 
00051| (** Collect all identifiers referenced in an expression/statement, excluding
00052|     property names on the right side of member ...
readcompleted
lib/scope.mli
Output
<file>
00001| (** Scope analysis for TypeScript ESTree AST.
00002| 
00003|     Walks the AST to build a scope tree, identify variable declarations, and
00004|     compute free variables for each arrow function (closure). *)
00005| 
00006| (** Analysis errors. *)
00007| type error =
00008|   | Expected_program_node
00009|       (** The [analyze] entry point was called with a non-[Program] AST node. *)
00010| 
00011| val error_to_string : error -> string
00012| (** [error_to_string err] is a human-readable message for [err]. *)
00013| 
00014| type var_info = {
00015|   name : string;
00016|   is_mutable_state : bool;
00017|       (** [true] when the initialiser is [new Map()], [new Set()], etc. *)
00018|   is_function : bool;
00019|       (** [true] when the initialiser is an [ArrowFunctionExpression]. *)
00020| }
00021| (** Information about a declared variable. *)
00022| 
00023| type closure_info = {
00024|   name : string;  (** The [const] name this arrow function is assigned to. *)
00025|   free_vars : string list;  (** Variables referenced from outer scopes. *)
00026|   called_functions : string list;
00027|       (** Names of other module-level functions called in the body. *)
00028|   captures_mutable : string list;
00029|       (** Subset of [free_vars] that are mutable state variables. *)
00030| }
00031| (** Per-closure analysis result. *)
00032| 
00033| type analysis_result = {
00034|   closures : closure_info list;
00035|   module_vars : var_info list;  (** All module-scope variable declarations. *)
00036|   exports : string list;
00037|       (** Names appearing in [export { ... }] declarations. *)
00038| }
00039| (** Complete analysis result for a program. *)
00040| 
00041| val analyze : Ast.node -> (analysis_result, error) result
00042| 

(End of file - total 42 lines)
</file>
700in / 387out / 78reasoning
assistantFeb 10, 05:01:59 AM
glm-4.7 · zai-coding-plan
Now I have all the files I need. Let me analyze each pass and produce comprehensive documentation. I'll also need to look at some supporting types and utilities to fully understand the passes. Let me also look at some supporting files that are imported: 1. The AliasingEffects types 2. The HIR types 3. The effect inference utilities Let me read these additional files to get a complete picture.

Now let me read some supporting files to better understand the data structures and types used in these passes.

readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {CompilerDiagnostic} from '../CompilerError';
00009| import {
00010|   FunctionExpression,
00011|   GeneratedSource,
00012|   Hole,
00013|   IdentifierId,
00014|   ObjectMethod,
00015|   Place,
00016|   SourceLocation,
00017|   SpreadPattern,
00018|   ValueKind,
00019|   ValueReason,
00020| } from '../HIR';
00021| import {FunctionSignature} from '../HIR/ObjectShape';
00022| import {printSourceLocation} from '../HIR/PrintHIR';
00023| 
00024| /**
00025|  * `AliasingEffect` describes a set of "effects" that an instruction/terminal has on one or
00026|  * more values in a program. These effects include mutation of values, freezing values,
00027|  * tracking data flow between values, and other specialized cases.
00028|  */
00029| export type AliasingEffect =
00030|   /**
00031|    * Marks the given value and its direct aliases as frozen.
00032|    *
00033|    * Captured values are *not* considered frozen, because we cannot be sure that a previously
00034|    * captured value will still be captured at the point of the freeze.
00035|    *
00036|    * For example:
00037|    * const x = {};
00038|    * const y = [x];
00039|    * y.pop(); // y dosn't contain x anymore!
00040|    * freeze(y);
00041|    * mutate(x); // safe to mutate!
00042|    *
00043|    * The exception to this is FunctionExpressions - since it is impossible to change which
00044|    * value a function closes over[1] we can transitively freeze functions and their captures.
00045|    *
00046|    * [1] Except for `let` values that are reassigned and closed over by a function, but we
00047|    * handle this explicitly with StoreContext/LoadContext.
00048|    */
00049|   | {kind: 'Freeze'; value: Place; reason: ValueReason}
00050|   /**
00051|    * Mutate ...
readerror
Error: File not found: /home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/Effect.ts
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {BindingKind} from '@babel/traverse';
00009| import * as t from '@babel/types';
00010| import {
00011|   CompilerDiagnostic,
00012|   CompilerError,
00013|   ErrorCategory,
00014| } from '../CompilerError';
00015| import {assertExhaustive} from '../Utils/utils';
00016| import {Environment, ReactFunctionType} from './Environment';
00017| import type {HookKind} from './ObjectShape';
00018| import {Type, makeType} from './Types';
00019| import {z} from 'zod/v4';
00020| import type {AliasingEffect} from '../Inference/AliasingEffects';
00021| import {isReservedWord} from '../Utils/Keyword';
00022| import {Err, Ok, Result} from '../Utils/Result';
00023| 
00024| /*
00025|  * *******************************************************************************************
00026|  * *******************************************************************************************
00027|  * ************************************* Core Data Model *************************************
00028|  * *******************************************************************************************
00029|  * *******************************************************************************************
00030|  */
00031| 
00032| // AST -> (lowering) -> HIR -> (analysis) -> Reactive Scopes -> (codegen) -> AST
00033| 
00034| /*
00035|  * A location in a source file, intended to be used for providing diagnostic information and
00036|  * transforming code while preserving source information (ie to emit source maps).
00037|  *
00038|  * `GeneratedSource` indicates that there is no single source location from which the code derives.
00039|  */
00040| export const GeneratedSource = Symbol();
00041| export type SourceLocation = t.SourceLocation | typeof Generate...
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/HIR/Dominator.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import prettyFormat from 'pretty-format';
00009| import {CompilerError} from '../CompilerError';
00010| import {BlockId, GeneratedSource, HIRFunction} from './HIR';
00011| import {eachTerminalSuccessor} from './visitors';
00012| 
00013| /*
00014|  * Computes the dominator tree of the given function. The returned `Dominator` stores the immediate
00015|  * dominator of each node in the function, which can be retrieved with `Dominator.prototype.get()`.
00016|  *
00017|  * A block X dominates block Y in the CFG if all paths to Y must flow through X. Thus the entry
00018|  * block dominates all other blocks. See https://en.wikipedia.org/wiki/Dominator_(graph_theory)
00019|  * for more.
00020|  */
00021| export function computeDominatorTree(fn: HIRFunction): Dominator<BlockId> {
00022|   const graph = buildGraph(fn);
00023|   const nodes = computeImmediateDominators(graph);
00024|   return new Dominator(graph.entry, nodes);
00025| }
00026| 
00027| /*
00028|  * Similar to `computeDominatorTree()` but computes the post dominators of the function. The returned
00029|  * `PostDominator` stores the immediate post-dominators of each node in the function.
00030|  *
00031|  * A block Y post-dominates block X in the CFG if all paths from X to the exit must flow through Y.
00032|  * The caller must specify whether to consider `throw` statements as exit nodes. If set to false,
00033|  * only return statements are considered exit nodes.
00034|  */
00035| export function computePostDominatorTree(
00036|   fn: HIRFunction,
00037|   options: {includeThrowsAsExitNode: boolean},
00038| ): PostDominator<BlockId> {
00039|   const graph = buildReverseGraph(fn, options.includeThrowsAsExitNode);
00040|   const nodes = computeImmediateDominator...
29,870in / 250out / 83reasoning
assistantFeb 10, 05:02:06 AM
glm-4.7 · zai-coding-plan
Let me also look at the InferMutationAliasingEffects and InferMutationAliasingRanges files to understand the mini-pipeline that runs on inner functions.

Let me also look at the mutation aliasing inference passes that are part of the mini-pipeline.

readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {
00009|   CompilerDiagnostic,
00010|   CompilerError,
00011|   Effect,
00012|   SourceLocation,
00013|   ValueKind,
00014| } from '..';
00015| import {
00016|   BasicBlock,
00017|   BlockId,
00018|   DeclarationId,
00019|   Environment,
00020|   FunctionExpression,
00021|   GeneratedSource,
00022|   getHookKind,
00023|   HIRFunction,
00024|   Hole,
00025|   IdentifierId,
00026|   Instruction,
00027|   InstructionKind,
00028|   InstructionValue,
00029|   isArrayType,
00030|   isJsxType,
00031|   isMapType,
00032|   isPrimitiveType,
00033|   isRefOrRefValue,
00034|   isSetType,
00035|   makeIdentifierId,
00036|   Phi,
00037|   Place,
00038|   SpreadPattern,
00039|   Type,
00040|   ValueReason,
00041| } from '../HIR';
00042| import {
00043|   eachInstructionValueOperand,
00044|   eachPatternItem,
00045|   eachTerminalOperand,
00046|   eachTerminalSuccessor,
00047| } from '../HIR/visitors';
00048| import {Ok, Result} from '../Utils/Result';
00049| import {
00050|   assertExhaustive,
00051|   getOrInsertDefault,
00052|   getOrInsertWith,
00053|   Set_isSuperset,
00054| } from '../Utils/utils';
00055| import {
00056|   printAliasingEffect,
00057|   printAliasingSignature,
00058|   printIdentifier,
00059|   printInstruction,
00060|   printInstructionValue,
00061|   printPlace,
00062| } from '../HIR/PrintHIR';
00063| import {FunctionSignature} from '../HIR/ObjectShape';
00064| import prettyFormat from 'pretty-format';
00065| import {createTemporaryPlace} from '../HIR/HIRBuilder';
00066| import {
00067|   AliasingEffect,
00068|   AliasingSignature,
00069|   hashEffect,
00070|   MutationReason,
00071| } from './AliasingEffects';
00072| import {ErrorCategory} from '../CompilerError';
00073| 
00074| const DEBUG = false;...
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts
Output
<file>
00001| /**
00002|  * Copyright (c) Meta Platforms, Inc. and affiliates.
00003|  *
00004|  * This source code is licensed under the MIT license found in the
00005|  * LICENSE file in the root directory of this source tree.
00006|  */
00007| 
00008| import {CompilerError, SourceLocation} from '..';
00009| import {
00010|   BlockId,
00011|   Effect,
00012|   HIRFunction,
00013|   Identifier,
00014|   IdentifierId,
00015|   InstructionId,
00016|   isJsxType,
00017|   makeInstructionId,
00018|   ValueKind,
00019|   ValueReason,
00020|   Place,
00021|   isPrimitiveType,
00022| } from '../HIR/HIR';
00023| import {
00024|   eachInstructionLValue,
00025|   eachInstructionValueOperand,
00026|   eachTerminalOperand,
00027| } from '../HIR/visitors';
00028| import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
00029| import {Err, Ok, Result} from '../Utils/Result';
00030| import {AliasingEffect, MutationReason} from './AliasingEffects';
00031| 
00032| /**
00033|  * This pass builds an abstract model of the heap and interprets the effects of the
00034|  * given function in order to determine the following:
00035|  * - The mutable ranges of all identifiers in the function
00036|  * - The externally-visible effects of the function, such as mutations of params and
00037|  *   context-vars, aliasing between params/context-vars/return-value, and impure side
00038|  *   effects.
00039|  * - The legacy `Effect` to store on each Place.
00040|  *
00041|  * This pass builds a data flow graph using the effects, tracking an abstract notion
00042|  * of "when" each effect occurs relative to the others. It then walks each mutation
00043|  * effect against the graph, updating the range of each node that would be reachable
00044|  * at the "time" that the effect occurred.
00045|  *
00046|  * This pass also validates against invalid effects: any function that is reachable
00047|  * by being called, or via a Render effect, is validated against mutating globals
00048|  * or calli...
grepcompleted
export type Effect =
Output
No files found
8,920in / 172out / 30reasoning
assistantFeb 10, 05:02:28 AM
glm-4.7 · zai-coding-plan
Now I have all the key files. Let me search for the Effect type definition.
grepcompleted
enum Effect
Output
Found 1 matches
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts:
  Line 1511: export enum Effect {
grepcompleted
export.*Effect =
Output
No files found
26,231in / 97out / 17reasoning
assistantFeb 10, 05:02:36 AM
glm-4.7 · zai-coding-plan
Let me read the Effect enum definition from HIR.ts.
readcompleted
react/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
Output
<file>
01501|   ValueReason.HookReturn,
01502|   ValueReason.JsxCaptured,
01503|   ValueReason.KnownReturnSignature,
01504|   ValueReason.Other,
01505|   ValueReason.ReactiveFunctionArgument,
01506|   ValueReason.ReducerState,
01507|   ValueReason.State,
01508| ]);
01509| 
01510| // The effect with which a value is modified.
01511| export enum Effect {
01512|   // Default value: not allowed after lifetime inference
01513|   Unknown = '<unknown>',
01514|   // This reference freezes the value (corresponds to a place where codegen should emit a freeze instruction)
01515|   Freeze = 'freeze',
01516|   // This reference reads the value
01517|   Read = 'read',
01518|   // This reference reads and stores the value
01519|   Capture = 'capture',
01520|   ConditionallyMutateIterator = 'mutate-iterator?',
01521|   /*
01522|    * This reference *may* write to (mutate) the value. This covers two similar cases:
01523|    * - The compiler is being conservative and assuming that a value *may* be mutated
01524|    * - The effect is polymorphic: mutable values may be mutated, non-mutable values
01525|    *   will not be mutated.
01526|    * In both cases, we conservatively assume that mutable values will be mutated.
01527|    * But we do not error if the value is known to be immutable.
01528|    */
01529|   ConditionallyMutate = 'mutate?',
01530| 
01531|   /*
01532|    * This reference *does* write to (mutate) the value. It is an error (invalid input)
01533|    * if an immutable value flows into a location with this effect.
01534|    */
01535|   Mutate = 'mutate',
01536|   // This reference may alias to (mutate) the value
01537|   Store = 'store',
01538| }
01539| export const EffectSchema = z.enum([
01540|   Effect.Read,
01541|   Effect.Mutate,
01542|   Effect.ConditionallyMutate,
01543|   Effect.ConditionallyMutateIterator,
01544|   Effect.Capture,
01545|   Effect.Store,
01546|   Effect.Freeze,
01547| ]);
01548| 
01549| export function isMutableEffect(
01550|   effect: Effect,

(Fil...
53in / 61out / 12reasoning
assistantFeb 10, 05:02:39 AM
glm-4.7 · zai-coding-plan
Now I have all the information I need to produce a comprehensive analysis. Let me compile the complete documentation for each pass.

Now I have all the necessary information. Let me produce my complete analysis:


Complete Analysis: React Compiler Function Analysis and Effect Inference Passes


1. AnalyseFunctions

File

/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts

Purpose

The key pass that performs recursive nested function analysis. It processes each inner function (function expressions and object methods) with a mini-pipeline of effect inference passes, then classifies context variables as either Capture or Read based on the function's behavior. This enables the outer function to understand what inner functions do without needing to re-analyze their bodies later.

Input Invariants

  • Input function (HIRFunction) must have its body.blocks map populated
  • instr.value.loweredFunc.func.context is populated for each FunctionExpression/ObjectMethod
  • Each context variable has a mutableRange and scope that can be reset

Output Guarantees

  • Each fn.aliasingEffects contains the externally-visible effects of that function
  • Each context variable has its operand.effect set to either Effect.Capture or Effect.Read
  • Context variable mutableRange is reset to {start: 0, end: 0} and scope is set to null
  • Apply effects are replaced with more precise effects (assertion enforced)

Algorithm (detailed, step-by-step)

Outer Pass (analyseFunctions function):

  1. Iterate over all blocks in the function body
  2. For each instruction in each block:
    • If instruction is a FunctionExpression or ObjectMethod:
      a. Call lowerWithMutationAliasing on the inner function
      b. Reset mutableRange for each context variable to {start: 0, end: 0}
      c. Reset scope for each context variable to null

Inner Mini-Pipeline (lowerWithMutationAliasing function):

Phase 1 - Effect Inference:

  1. Recursively call analyseFunctions on the inner function (handles nested lambdas)
  2. Call inferMutationAliasingEffects(fn, {isFunctionExpression: true})
    • This performs abstract interpretation to infer mutation/aliasing effects
    • Marks each instruction with an array of AliasingEffect
  3. Call deadCodeElimination(fn) - removes dead code
  4. Call inferMutationAliasingRanges(fn, {isFunctionExpression: true}).unwrap()
    • Builds an alias graph from effects
    • Propagates mutations forward and backward through the graph
    • Computes mutable ranges for all identifiers
    • Returns externally-visible function effects
  5. Call rewriteInstructionKindsBasedOnReassignment(fn) - rewrites instructions
  6. Call inferReactiveScopeVariables(fn) - determines reactive scopes
  7. Store the effects in fn.aliasingEffects

Phase 2 - Context Variable Classification:

  1. Create a set capturedOrMutated of IdentifierId

  2. Iterate over all functionEffects returned from Phase 1:

    • For Capture effects (line 72): capturedOrMutated.add(effect.from.identifier.id)
    • For Alias effects (line 71): capturedOrMutated.add(effect.from.identifier.id)
    • For Assign effects (line 70): capturedOrMutated.add(effect.from.identifier.id)
    • For CreateFrom effects (line 73): capturedOrMutated.add(effect.from.identifier.id)
    • For MaybeAlias effects (line 74): capturedOrMutated.add(effect.from.identifier.id)
    • For Mutate effects (line 84-89): capturedOrMutated.add(effect.value.identifier.id)
    • For MutateConditionally/MutateTransitive/MutateTransitiveConditionally: same as Mutate
    • For Apply effects (line 78-83): ASSERTION ERROR - should not exist
    • For ImmutableCapture, Freeze, Create, Impure, Render, MutateFrozen, MutateGlobal, CreateFunction: no-op (not capture)
  3. For each context variable operand:

    • If capturedOrMutated.has(operand.identifier.id) OR operand.effect === Effect.Capture:
      • Set operand.effect = Effect.Capture
    • Else:
      • Set operand.effect = Effect.Read

Key Data Structures

HIRFunction:

{
  context: Array<Place>,  // Captured variables from outer scope
  aliasingEffects: Array<AliasingEffect> | null,
  // ... other fields
}

Place with Effect:

{
  identifier: Identifier,
  effect: Effect.Read | Effect.Capture | ...,
  reactive: boolean
}

AliasingEffect Types (capture-inducing vs non-capture-inducing):

Effect Captures? Why
Capture Direct capture relationship
Alias May alias the value
Assign Assignment from source
MaybeAlias Potential aliasing
CreateFrom Created from source value
Mutate Mutates the value
MutateConditionally May mutate
MutateTransitive Transitively mutates
MutateTransitiveConditionally May transitively mutate
ImmutableCapture Read-only capture
Freeze Freezing doesn't capture
Create Creates new value
Impure Side effect, not capture
Render Render-time access
MutateFrozen Error state
MutateGlobal Error state
CreateFunction Function creation

Edge Cases

  • Apply effects assertion: If an Apply effect exists in the function effects, it indicates a bug - Apply should have been replaced with more precise effects by the inference passes.
  • Context variable mutable ranges: The comment notes that inferReactiveScopeVariables makes identifiers point to the same mutableRange instance, so resetting start/end is insufficient - the entire range object must be replaced.
  • Effect already set: The final classification checks if operand.effect === Effect.Capture before setting it, meaning if it was already classified as Capture, it stays Capture regardless of other effects.

Mapping to CloudLang

CloudLang's scope.ml does a simpler version:

CloudLang:

type closure_info = {
  name : string;
  free_vars : string list;           (* Free variables from outer scope *)
  called_functions : string list;      (* Functions called by this closure *)
  captures_mutable : string list;      (* Free vars that are mutable state *)
}

React's AnalyseFunctions:

  • free_vars ≈ All context variables (fn.context)
  • captures_mutable ≈ Context variables with Effect.Capture
  • Called functions are tracked separately in React through effect signatures

Key Delta:

  1. React classifies all context variables as either Capture or Read based on fine-grained effect analysis
  2. CloudLang only separates free_vars into captures_mutable (mutable state) vs regular free vars
  3. React tracks exact mutation patterns (Mutate vs MutateTransitive, conditional vs definite)
  4. React resets mutable ranges after analysis for use by outer function

Example (before/after)

Before:

function Component(props) {
  const data = useData(); // Hook returns stable object
  
  const handleClick = () => {
    // Inner function capturing props.data
    console.log(props.data);
  };
  
  return <Button onClick={handleClick}>Click</Button>;
}

After AnalyseFunctions (simplified HIR representation):

Inner function (handleClick):
  context: [
    { identifier: "props", effect: Effect.Capture },
    { identifier: "data", effect: Effect.Read }  // Not captured, just read
  ]
  aliasingEffects: [
    { kind: "Render", place: <props> }
  ]

Outer function context classification:
  { identifier: "handleClick", effect: Effect.Capture }
  // The inner function itself is captured

2. InferReactivePlaces

File

/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts

Purpose

Determines which values are "reactive" (may semantically change over the component/hook's lifetime). Reactive values include props, hook results, values mutated with reactive operands, and values conditionally assigned based on reactive conditions.

Input Invariants

  • fn.aliasingEffects is populated
  • fn.context is populated with classified effects
  • Mutation ranges have been computed by previous passes

Output Guarantees

  • Each Place.reactive is set to true if the value is reactive
  • The ReactivityMap tracks reactive identifiers using a disjoint set for aliased values
  • Fixpoint iteration ensures reactivity propagates through all dataflow paths

Algorithm (detailed, step-by-step)

Setup:

  1. Create ReactivityMap with disjoint set of mutably-aliased identifiers
  2. Mark all function parameters as reactive (they're inputs that can change)
  3. Create StableSidemap to track stable value sources
  4. Compute isReactiveControlledBlock using dominator analysis

Fixpoint Iteration Loop:

do {
  For each block:
    1. Mark phis reactive if any operand is reactive
    2. Mark phis reactive if any predecessor is reactive-controlled
    3. For each instruction:
       a. Update stability information
       b. Check for reactive inputs
       c. If inputs reactive:
          - Mark all lvalues as reactive (unless stable-typed)
          - If operands have mutation effects (Capture, Store, etc.):
            Mark operands as reactive if they're mutable

} while (reactiveIdentifiers.snapshot() returns true - changes occurred)


**Propagation to Inner Functions:**
After fixpoint converges, recursively propagate reactivity for inner functions to account for hoisting/deduping of dependency instructions.

### Key Data Structures

**StableSidemap** - Tracks sources of stability:
```typescript
class StableSidemap {
  map: Map<IdentifierId, {isStable: boolean}>
  
  handleInstruction(instr: Instruction): void {
    // CallExpression/MethodCall: Mark stable if evaluates to stable type/container
    // PropertyLoad/Destructure: Propagate stability from source
    // StoreLocal/LoadLocal: Propagate stability
  }
}

ReactivityMap - Tracks reactivity with alias support:

class ReactivityMap {
  hasChanges: boolean
  reactive: Set<IdentifierId>
  aliasedIdentifiers: DisjointSet<Identifier>  // Unified alias groups
  
  isReactive(place: Place): boolean {
    const identifier = aliasedIdentifiers.find(place.identifier) ?? place.identifier
    return reactive.has(identifier.id)
  }
  
  markReactive(place: Place): void {
    const identifier = aliasedIdentifiers.find(place.identifier) ?? place.identifier
    if (!reactive.has(identifier.id)) {
      reactive.add(identifier.id)
      hasChanges = true
    }
  }
}

Edge Cases

  1. Stable types override: Even if reactive inputs, values with isStableType (like refs) are not marked reactive
  2. Conditional reactivity: Values assigned based on reactive conditions become reactive even if the assigned value is constant
  3. Transitive mutation: z = [x] where x is later mutated makes z reactive due to transitive reactivity propagation
  4. Fixpoint required: Without iteration, we miss reactivity that propagates through aliases and conditionally-assigned values

Example

function Component(props) {
  let x;
  if (props.reactive) {  // reactive condition
    x = 1;
  } else {
    x = 2;
  }
  // x becomes reactive even though 1 and 2 are constants!
  return x;
}

3. InferEffectDependencies

File

/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts

Purpose

Infers reactive dependencies captured by useEffect lambdas and automatically adds them as the second argument if no dependency array is provided. This implements the "auto-dependencies" feature of the React Compiler.

Input Invariants

  • Hook calls (useEffect, useLayoutEffect, etc.) are configured for auto-dependencies
  • Reactive scopes have been computed for the function body
  • fn.aliasingEffects exists for inner functions

Output Guarantees

  • useEffect calls without deps arrays get auto-generated deps
  • Place.reactive flags are retained for dependency inference
  • Codegen receives proper dependency array information

Algorithm (detailed, step-by-step)

Phase 1 - Setup:

  1. Collect all FunctionExpression instructions into fnExpressions map
  2. Build autodepFnConfigs map from env.config.inferEffectDependencies
  3. Build loadGlobals set of global function loads

Phase 2 - Process Instructions:
For each instruction in each block:

  • If FunctionExpression: Add to fnExpressions map
  • If PropertyLoad: Check if it's loading an auto-dep function from a module
  • If LoadGlobal: Track React imports and configured auto-dep functions
  • If CallExpression or MethodCall:
    1. Find autodepsArgExpectedIndex from callee
    2. Check if call matches expected signature (no deps array provided)
    3. If matching:
      a. Build effect deps array: effectDeps
      b. Get the function expression from fnExpressions.get(value.args[0].identifier.id)
      c. Case 1: Inline function:
      • Get scope info or call inferMinimalDependencies(fnExpr)
      • For each dependency:
        • Skip non-reactive refs/sets/fire functions
        • Build dependency instructions using buildDependencyInstructions
        • Add dependency place to effectDeps array
      • Insert deps array instruction before the call
        d. Case 2: Global function:
      • Insert empty deps array
        e. Mark location as having inferred effect
        f. Replace the deps argument with a reference to the new deps array

Phase 3 - Minimal Dependency Inference (inferMinimalDependencies):

  1. Collect temporaries and optional chain sidemaps
  2. Collect hoistable property loads at function entry
  3. Call inferDependencies with context tracking
  4. Build dependency tree and derive minimal dependencies
  5. Filter to only include context variables (free variables)

Phase 4 - Code Splicing:
Use rewriteSplices to insert new instructions into blocks:

  • Split blocks at splice locations
  • Insert dependency instructions
  • Merge with existing code
  • Fix up RPO, predecessors, and instruction IDs

Key Data Structures

ManualMemoDependency:

{
  root: { kind: 'Global' | 'NamedLocal', ... },
  path: Array<{property: string, optional: boolean}>,
  loc: SourceLocation
}

ReactiveScopeDependency:

{
  identifier: Identifier,
  path: Array<{property: string, optional: boolean}>,
  ...
}

Edge Cases

  1. Non-reactive dependencies: Non-reactive refs, setters, and fire functions are excluded from auto-deps
  2. Optional chaining: Dependencies accessed through optional chains (obj?.prop) require special handling
  3. Truncate at .current: ref.current dependencies are truncated to just ref
  4. Global functions: Get empty dependency arrays
  5. Auto-deps eligible locations: Even manually-specified deps can be eligible for LSP decoration

Example

// Before
useEffect(() => {
  console.log(props.count);
  console.log(data.value);
});  // No deps array!

// After
useEffect(() => {
  console.log(props.count);
  console.log(data.value);
}, [props.count, data]);  // Auto-inferred deps!

4. DropManualMemoization

File

/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts

Purpose

Removes useMemo and useCallback APIs by transforming them into direct function calls or references. This enables the compiler to take over memoization based on its own analysis.

Input Invariants

  • Function is in pre-SSA form (before SSA transformation)
  • useMemo/useCallback calls are identified via global loads or property loads

Output Guarantees

  • useMemo(callback, deps) becomes callback() (direct call)
  • useCallback(callback, deps) becomes callback (direct reference)
  • Optional validation markers inserted for validation modes
  • Original deps arrays become dead code (removed by DCE)

Algorithm (detailed, step-by-step)

Phase 1 - Collection:

  1. Build sidemap with:
    • functions: All FunctionExpression instructions
    • manualMemos: Identified useMemo/useCallback load instructions
    • react: Direct React global loads
    • maybeDeps: Tracked manual memo dependencies
    • maybeDepsLists: Array literal dependencies
    • optionals: Optional chain places

Phase 2 - Transform:
For each instruction:

  • If it's a useMemo/useCallback call:
    1. Extract function and deps using extractManualMemoizationArgs
    2. Transform instruction:
      • useMemo: Replace with CallExpression where callee = fnArg, args = []
      • useCallback: Replace with LoadLocal of fnArg
    3. If validation enabled:
      • Create StartMemoize marker with deps list
      • Create FinishMemoize marker after the call
      • Insert markers at appropriate positions

Phase 3 - Insert Markers:
Use queuedInserts map to add markers at specific instruction IDs:

  • StartMemoize after the hook load instruction
  • FinishMemoize after the transformed call

Key Data Structures

ManualMemoCallee:

{
  kind: 'useMemo' | 'useCallback',
  loadInstr: LoadGlobal | PropertyLoad
}

IdentifierSidemap:

{
  functions: Map<IdentifierId, FunctionExpression>,
  manualMemos: Map<IdentifierId, ManualMemoCallee>,
  react: Set<IdentifierId>,
  maybeDeps: Map<IdentifierId, ManualMemoDependency>,
  maybeDepsLists: Map<IdentifierId, {loc, deps: Place[]}>
}

Edge Cases

  1. Non-inline functions: Validation requires inline functions (sketchy to use opaque function)
  2. Missing deps array: Works with null deps
  3. React namespace: Handles both React.useMemo and direct useMemo imports
  4. useMemo void returns: Validates that useMemo returns a value

Example

// Before
const memoized = useMemo(() => {
  return expensiveCalc(props.data);
}, [props.data]);

// After
const memoized = (() => {
  return expensiveCalc(props.data);
})();  // IIFE, will be inlined

5. InlineImmediatelyInvokedFunctionExpressions

File

/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts

Purpose

Inlines IIFEs (Immediately Invoked Function Expressions) to allow more fine-grained memoization. This reduces function call overhead and enables better dependency tracking.

Input Invariants

  • Functions have been lowered to HIR
  • Block structure is intact
  • IIFEs have no arguments

Output Guarantees

  • IIFE bodies are spliced into the call site
  • Return statements are replaced with assignments + gotos
  • Control flow is preserved using labeled blocks or direct gotos

Algorithm (detailed, step-by-step)

Detection:

  1. Track all FunctionExpression instructions assigned to temporaries
  2. For each CallExpression:
    • Check if callee is a local function expression
    • Verify no arguments
    • Verify no params, async, or generator

Inlining - Single Return (Simple Case):

  1. Split block: create continuation block with code after the call
  2. Replace original block's terminal with goto to function entry
  3. Replace return terminals with:
    • StoreLocal of return value to the IIFE's lvalue
    • goto to continuation block
  4. Add all function blocks to outer function

Inlining - Multiple Returns (Complex Case):

  1. Insert LabelTerminal before the IIFE call
  2. Declare temporary variable for the result
  3. Replace return terminals with:
    • StoreLocal of return value to result temporary
    • goto to label's fallthrough (continuation)
  4. Splice function blocks "between" the label and continuation

Cleanup:

  1. Remove inlined function definition instructions
  2. Reverse postorder blocks
  3. Renumber instructions
  4. Mark predecessors
  5. Merge consecutive blocks

Key Data Structures

  • GotoVariant.Break: Used for labeled block gotos
  • LabelTerminal: Controls control flow for inlined returns

Edge Cases

  1. Single return optimization: Avoids labeled block overhead
  2. Statement block requirement: Cannot inline IIFEs in expression blocks
  3. Nested IIFEs: Sequential IIFEs handled by pushing continuation blocks to queue

Example

// Before
const x = (() => {
  const temp = [];
  temp.push(props.value);
  return temp;
})();

// After (inlined HIR)
let t0;
Label block=bb1 fallthrough=bb2
bb1:
  const temp = [];
  temp.push(props.value);
  t0 = temp;  // replaced return
  Goto bb2    // instead of return
bb2:
  const x = t0;

6. ControlDominators

File

/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/Inference/ControlDominators.ts

Purpose

Computes which blocks are "controlled" by reactive values using post-dominator analysis. Used to determine if a block's execution depends on a reactive condition.

Input Invariants

  • Post-dominator tree has been computed
  • isControlVariable predicate is provided

Output Guarantees

  • Returns a function ControlDominators that tests if a block ID is controlled by reactive values
  • Results are cached in postDominatorFrontierCache

Algorithm (detailed, step-by-step)

Post-Dominator Frontier Computation:

  1. Compute post-dominator tree for the function
  2. For each block:
    • Find its post-dominator frontier
    • Frontier = immediate successors of nodes that post-dominate the block
    • These are the earliest blocks where execution may branch away

Control Block Test:
For a given block:

  1. Get its post-dominator frontier (cached)
  2. For each frontier block:
    • Check if the terminal's test variable is reactive
    • For if/branch: check terminal.test
    • For switch: check terminal.test and case tests
  3. If any frontier block has a reactive test, the block is controlled

Key Data Structures

Post-Dominator Frontier:

  • Set of blocks from which execution may or may not reach the target
  • Computed by finding successors of post-dominating blocks

ControlDominators type:

type ControlDominators = (id: BlockId) => boolean

Edge Cases

  1. Exit node handling: Throws can be excluded from exit nodes
  2. Cache usage: Computation is expensive, so results are cached
  3. Multiple reactive tests: Any reactive test in the frontier makes the block controlled

Example

if (props.condition) {  // reactive test
  x = 1;
} else {
  x = 2;
}
return x;

The block after the if is controlled by props.condition. Even though x is assigned constant values, it becomes reactive due to the reactive control flow.


CloudLang Comparison and Upgrade Path

Current CloudLang Scope Analysis

File: /home/jm/data/ai/cloudlang-trees/react/lib/scope.ml

Current Analysis:

type var_info = { 
  name : string; 
  is_mutable_state : bool;   (* new Map(), new Set(), new Array() *)
  is_function : bool          (* arrow function *)
}

type closure_info = {
  name : string;
  free_vars : string list;           (* All outer-scope references *)
  called_functions : string list;      (* Module functions called *)
  captures_mutable : string list;    (* Free vars that are mutable state *)
}

Algorithm:

  1. Two-pass analysis:
    • Pass 1: Collect module-scope declarations
    • Pass 2: For each arrow function, compute:
      • free_vars: References not locally declared
      • called_functions: Functions called as simple identifiers
      • captures_mutable: Subset of free_vars that are is_mutable_state

Delta: React vs CloudLang

Aspect React CloudLang
Capture Classification Capture vs Read (fine-grained) captures_mutable vs free_vars
Effect Tracking Full AliasingEffects (16 types) Just mutable vs immutable
Mutation Analysis Conditional/Definite, Transitive Simple mutable flag
Reactivity Propagates through dataflow with fixpoint Not tracked
Mutable Ranges Start/end instruction IDs Not tracked
Function Signature Full aliasing effects Not modeled
Stability StableSidemap for refs/hooks Not tracked

Proposed OCaml Module Signature

(** Effect classification for captured variables *)
type effect =
  | Read  (** Read-only access *)
  | Capture  (** Mutable capture - may mutate *)
  
(** Mutation kind *)
type mutation_kind =
  | Definite  (** Definitely mutates *)
  | Conditional  (** May mutate *)

(** Alias/capture relationship *)
type alias_effect =
  | Alias of Place.t * Place.t  (** a -> b, mutate(b) => mutate(a) *)
  | Capture of Place.t * Place.t  (** a captured into b, no transitive mutation *)
  | MaybeAlias of Place.t * Place.t  (** Potential alias *)
  | ImmutableCapture of Place.t * Place.t  (** Read-only capture *)

(** External function effects *)
type function_effects = {
  mutations : (Place.t * mutation_kind) list;  (** Params/context mutated *)
  captures : alias_effect list;  (** Data flow between params/return *)
  impure : bool;  (** Has side effects *)
}

(** Context variable with effect *)
type context_var = {
  name : string;
  effect : effect;
  identifier : Identifier.t;
}

(** Closure analysis result *)
type closure_info = {
  name : string;
  params : Place.t list;
  context : context_var list;
  effects : function_effects;
  aliasing_effects : alias_effect list;
  reactive : Place.t list;  (** Reactive free variables *)
}

(** Analysis result for program *)
type analysis_result = {
  closures : closure_info list;
  module_vars : (string * var_info) list;
  exports : string list;
}

(** Main analysis function *)
val analyze : Ast.node -> (analysis_result, error) result

(** Analyze a single closure *)
val analyze_closure : string -> Ast.node list -> Ast.node -> 
  var_info list -> closure_info

(** Effect inference on HIR *)
val infer_effects : Hir.function -> function_effects result

(** Reactivity inference *)
val infer_reactive : Hir.function -> Place.t Set.t

Binding Inference Mapping

In CloudLang, inner functions become separate Workers. The effect signature determines what bindings each Worker needs:

React Context → CloudLang Binding:

React Context CloudLang Binding
Effect.Capture of KV-type KV namespace binding
Effect.Read of KV-type KV read-only access
Effect.Capture of service-type Service binding
Effect.Capture of hook-result State binding
Render effect Durable object access
StableSidemap (ref) No binding needed (stable)

Example:

// React
const db = useDB();  // hook returning stable reference
const getData = () => {
  return db.get(props.id);  // captures db (stable) and props.id (reactive)
};

// CloudLang generated bindings
worker getData {
  bindings: {
    db: useDB_result,  // Effect.Read -> read-only
    id: props_id,       // Effect.Capture -> KV binding
  }
}

Key insight: React's Capture vs Read classification directly maps to CloudLang's mutable vs immutable bindings. Captured mutable values require the binding to be passed to the Worker; read-only values can be accessed globally or via read-only references.

631in / 6,491out / 24reasoning