Autogenerated summaries of each of the compiler passes which allow agents to get the key ideas of a compiler pass, including key input/output invariants, without having to reprocess the file each time. In the subsequent diff this seemed to help. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35595). * #35607 * #35298 * #35596 * #35573 * __->__ #35595 * #35539
6.8 KiB
buildReactiveFunction
File
src/ReactiveScopes/BuildReactiveFunction.ts
Purpose
The buildReactiveFunction pass converts the compiler's HIR (High-level Intermediate Representation) from a Control Flow Graph (CFG) representation to a tree-based ReactiveFunction representation that is closer to an AST. This is a critical transformation in the React Compiler pipeline that:
- Restores control flow constructs - Reconstructs
if,while,for,switch, and other control flow statements from the CFG's basic blocks and terminals - Eliminates phi nodes - Replaces SSA phi nodes with compound value expressions (ternaries, logical expressions, sequence expressions)
- Handles labeled break/continue - Tracks control flow targets to emit explicit labeled
breakandcontinuestatements when needed - Preserves reactive scope information - Scope terminals are converted to
ReactiveScopeBlocknodes in the tree
Input Invariants
- HIR is in SSA form (variables have been renamed with unique identifiers)
- Basic blocks are connected (valid predecessor/successor relationships)
- Each block ends with a valid terminal
- Phi nodes exist at merge points for values from different control flow paths
- Reactive scopes have been constructed (
scopeterminals exist) - Scope dependencies are computed (
PropagateScopeDependenciesHIRhas run)
Output Guarantees
- Tree structure - The output is a
ReactiveFunctionwith abody: ReactiveBlockcontaining a tree ofReactiveStatementnodes - No CFG structure - Basic blocks are eliminated; control flow is represented through nested reactive terminals
- No phi nodes - Value merges are represented as
ConditionalExpression,LogicalExpression, orSequenceExpressionvalues - Labels emitted for all control flow - Every terminal that can be a break/continue target has a label; unnecessary labels are removed by subsequent
PruneUnusedLabelspass - Each block emitted exactly once - A block cannot be generated twice
- Scope blocks preserved -
scopeterminals becomeReactiveScopeBlocknodes
Algorithm
Core Classes
Driver- Traverses blocks and emits ReactiveBlock arraysContext- Tracks state:emitted: Set<BlockId>- Which blocks have been generated#scheduled: Set<BlockId>- Blocks that will be emitted by parent constructs#controlFlowStack: Array<ControlFlowTarget>- Stack of active break/continue targetsscopeFallthroughs: Set<BlockId>- Fallthroughs for scope blocks
Traversal Strategy
- Start at the entry block and call
traverseBlock(entryBlock) - For each block:
- Emit all instructions as
ReactiveInstructionStatement - Process the terminal based on its kind
- Emit all instructions as
Terminal Processing
Simple Terminals:
return,throw- Emit directly asReactiveTerminalunreachable- No-op
Control Flow Terminals:
if- Schedule fallthrough, recursively traverse consequent/alternate, emitReactiveIfTerminalwhile,do-while,for,for-of,for-in- UsescheduleLoop()which tracks continue targetsswitch- Process cases in reverse orderlabel- Schedule fallthrough, traverse inner block
Value Terminals (expressions that produce values):
ternary,logical,optional,sequence- ProduceReactiveValuecompound expressions
Break/Continue:
gotowithGotoVariant.Break- Determine if break is implicit, unlabeled, or labeledgotowithGotoVariant.Continue- Determine continue type
Scope Terminals:
scope,pruned-scope- Schedule fallthrough, traverse inner block, emit asReactiveScopeBlock
Key Data Structures
ReactiveFunction
type ReactiveFunction = {
loc: SourceLocation;
id: ValidIdentifierName | null;
params: Array<Place | SpreadPattern>;
generator: boolean;
async: boolean;
body: ReactiveBlock;
env: Environment;
directives: Array<string>;
};
ReactiveBlock
type ReactiveBlock = Array<ReactiveStatement>;
ReactiveStatement
type ReactiveStatement =
| ReactiveInstructionStatement // {kind: 'instruction', instruction}
| ReactiveTerminalStatement // {kind: 'terminal', terminal, label}
| ReactiveScopeBlock // {kind: 'scope', scope, instructions}
| PrunedReactiveScopeBlock; // {kind: 'pruned-scope', ...}
ReactiveValue (for compound expressions)
type ReactiveValue =
| InstructionValue // Regular instruction values
| ReactiveLogicalValue // a && b, a || b, a ?? b
| ReactiveSequenceValue // (a, b, c)
| ReactiveTernaryValue // a ? b : c
| ReactiveOptionalCallValue; // a?.b()
ControlFlowTarget
type ControlFlowTarget =
| {type: 'if'; block: BlockId; id: number}
| {type: 'switch'; block: BlockId; id: number}
| {type: 'case'; block: BlockId; id: number}
| {type: 'loop'; block: BlockId; continueBlock: BlockId; ...};
Edge Cases
Nested Control Flow
The scheduling mechanism handles arbitrarily nested control flow by pushing/popping from the control flow stack.
Value Blocks with Complex Expressions
SequenceExpression handles cases where value blocks contain multiple instructions.
Scope Fallthroughs
Breaks to scope fallthroughs are treated as implicit (no explicit break needed).
Catch Handlers
Scheduled specially via scheduleCatchHandler() to prevent re-emission.
Unreachable Blocks
The reachable() check prevents emitting unreachable blocks.
TODOs
The code contains several CompilerError.throwTodo() calls for unsupported patterns:
- Optional chaining test blocks must end in
branch - Logical expression test blocks must end in
branch - Support for value blocks within try/catch statements
- Support for labeled statements combined with value blocks
Example
Fixture: ternary-expression.js
Input:
function ternary(props) {
const a = props.a && props.b ? props.c || props.d : (props.e ?? props.f);
const b = props.a ? (props.b && props.c ? props.d : props.e) : props.f;
return a ? b : null;
}
HIR (CFG with many basic blocks):
The HIR contains 33 basic blocks with Ternary, Logical, Branch, and Goto terminals, plus phi nodes at merge points.
ReactiveFunction Output (Tree):
function ternary(props$62{reactive}) {
[1] $84 = Ternary
Sequence
[2] $66 = Logical
Sequence [...]
&& Sequence [...]
?
Sequence [...] // props.c || props.d
:
Sequence [...] // props.e ?? props.f
[40] StoreLocal a$99 = $98
...
[82] return $145
}
The transformation eliminates:
- 33 basic blocks reduced to a single tree
- Phi nodes replaced with nested
TernaryandLogicalvalue expressions - CFG edges replaced with tree nesting