High-level Intermediate Representation (HIR)
Overview
The High-level Intermediate Representation (HIR) is the central data structure used by the React Compiler to analyze and transform React components and hooks. It provides a structured, flattened representation of JavaScript code that is more amenable to data-flow analysis than a standard Abstract Syntax Tree (AST).
The HIR allows the compiler to perform complex optimizations such as automatic memoization, alias tracking, and validation of React-specific rules (e.g., the Rules of Hooks).
Core Architecture
The HIR represents a function as a collection of basic blocks, each containing a sequence of instructions. This structure simplifies the identification of dependencies and the lifetime of variables.
HIRFunction
The HIRFunction is the top-level structure representing a component or hook. It contains:
env: TheEnvironmentconfiguration.params: An array ofPlaceobjects representing function arguments.body: AHIRBodyconsisting of a map ofBlockIdtoBasicBlock.
Instructions and Values
An Instruction is the fundamental unit of work in the HIR. Each instruction consists of:
id: A uniqueInstructionIdused for ordering and analysis.lvalue: APlacerepresenting where the result of the instruction is stored.value: AReactiveValuerepresenting the operation being performed (e.g.,BinaryExpression,CallExpression,LoadGlobal).
export type Instruction = {
id: InstructionId;
lvalue: Place;
value: ReactiveValue;
loc: SourceLocation;
};
Places and Identifiers
Identifier: Represents a unique variable. Unlike AST nodes, HIR identifiers are disambiguated; two variables with the same name in different scopes will have differentIdentifierIds.Place: A reference to anIdentifierat a specific point in the program.
Reactive Scopes
A "Reactive Scope" is a block of instructions that should be memoized together as a single unit. The compiler groups instructions into scopes based on their dependencies and mutations.
Scope Pruning
The compiler optimizes output by pruning scopes that do not "escape" the function. A value is considered to have escaped if:
- It is directly or transitively returned by the function.
- It is passed as an argument to a hook (since hooks may store references externally).
Dependency Tracking
For a scope to be correctly memoized, all of its transitive dependencies must also be memoized, even if those dependencies do not escape. This prevents "memoization breakage" where a dependency invalidates every render, causing the downstream scope to re-execute unnecessarily.
function Component(props) {
// 'a' does not escape, but it is a dependency of 'b'.
// If 'a' is not memoized, 'b' will invalidate every render.
const a = [props.a];
const b = [];
b.push(a);
return b; // 'b' escapes
}
Validation Passes
The HIR is used to enforce React-specific constraints through various validation passes.
Capitalized Call Validation
The compiler ensures that capitalized functions (likely React components) are not invoked as standard functions. Components must be invoked via JSX to ensure React can manage their lifecycle correctly.
// Internal logic for ValidateNoCapitalizedCalls
switch (value.kind) {
case 'CallExpression': {
const calleeName = capitalLoadGlobals.get(value.callee.identifier.id);
if (calleeName != null) {
CompilerError.throwInvalidReact({
reason: 'Capitalized functions are reserved for components, which must be invoked with JSX.',
description: `${calleeName} may be a component`,
loc: value.loc,
});
}
break;
}
}
Alias and Mutation Analysis
One of the primary strengths of HIR is its ability to track mutable aliases. The compiler analyzes instructions to determine if multiple identifiers point to the same underlying object.
LoadGlobal: Loads a global variable into a localPlace.PropertyLoad: Accesses a property on an object, tracking the relationship between the object and the result.MethodCall: Analyzes whether a method call might mutate the receiver or its arguments.
This analysis is critical for determining the boundaries of ReactiveScopes. If an object is mutated, all instructions involved in the creation and mutation of that object must reside within the same scope.
Internal Representation Summary
| Type | Purpose |
|:--- |:--- |
| ReactiveFunction | A transformed version of HIR used for emitting reactive code. |
| InstructionId | A numerical ID used to track the linear order of operations. |
| ScopeId | A unique identifier for a memoization block. |
| Effect | Metadata on a Place indicating if it is read, written, or captured. |
| Terminal | Instructions that exit a block (e.g., return, throw, if, for). |