[compiler] Prepare HIRBuilder to be used by later passes (#32286)
--- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32286). * #33326 * #33325 * __->__ #32286
mofeiZ committed
May 22, 2025 at 16:13 UTC
13f20044f3a5a9433eb4c6ef4c6577b8f0d13350
4 files changed
+34
-25
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+1
@@ -130,6 +130,7 @@ function run(
130
mode,
131
config,
132
contextIdentifiers,
133
+ func,
134
logger,
135
filename,
136
code,
compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
+12
-13
@@ -70,12 +70,14 @@ import {BuiltInArrayId} from './ObjectShape';
70
export function lower(
71
func: NodePath<t.Function>,
72
env: Environment,
73
+ // Bindings captured from the outer function, in case lower() is called recursively (for lambdas)
74
bindings: Bindings | null = null,
75
capturedRefs: Array<t.Identifier> = [],
75
- // the outermost function being compiled, in case lower() is called recursively (for lambdas)
76
- parent: NodePath<t.Function> | null = null,
76
): Result<HIRFunction, CompilerError> {
78
- const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs);
77
+ const builder = new HIRBuilder(env, {
78
+ bindings,
79
+ context: capturedRefs,
80
+ });
81
const context: HIRFunction['context'] = [];
82
83
for (const ref of capturedRefs ?? []) {
@@ -215,7 +217,7 @@ export function lower(
217
return Ok({
218
id,
219
params,
218
- fnType: parent == null ? env.fnType : 'Other',
220
+ fnType: bindings == null ? env.fnType : 'Other',
221
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
222
returnType: makeType(),
223
body: builder.build(),
@@ -3417,7 +3419,7 @@ function lowerFunction(
3419
| t.ObjectMethod
3420
>,
3421
): LoweredFunction | null {
3420
- const componentScope: Scope = builder.parentFunction.scope;
3422
+ const componentScope: Scope = builder.environment.parentFunction.scope;
3423
const capturedContext = gatherCapturedContext(expr, componentScope);
3424
3425
/*
@@ -3428,13 +3430,10 @@ function lowerFunction(
3430
* This isn't a problem in practice because use Babel's scope analysis to
3431
* identify the correct references.
3432
*/
3431
- const lowering = lower(
3432
- expr,
3433
- builder.environment,
3434
- builder.bindings,
3435
- [...builder.context, ...capturedContext],
3436
- builder.parentFunction,
3437
- );
3433
+ const lowering = lower(expr, builder.environment, builder.bindings, [
3434
+ ...builder.context,
3435
+ ...capturedContext,
3436
+ ]);
3437
let loweredFunc: HIRFunction;
3438
if (lowering.isErr()) {
3439
lowering
@@ -3456,7 +3455,7 @@ function lowerExpressionToTemporary(
3455
return lowerValueToTemporary(builder, value);
3456
}
3457
3459
-function lowerValueToTemporary(
3458
+export function lowerValueToTemporary(
3459
builder: HIRBuilder,
3460
value: InstructionValue,
3461
): Place {
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+4
-1
@@ -47,7 +47,7 @@ import {
47
ShapeRegistry,
48
addHook,
49
} from './ObjectShape';
50
-import {Scope as BabelScope} from '@babel/traverse';
50
+import {Scope as BabelScope, NodePath} from '@babel/traverse';
51
import {TypeSchema} from './TypeSchema';
52
53
export const ReactElementSymbolSchema = z.object({
@@ -675,6 +675,7 @@ export class Environment {
675
676
#contextIdentifiers: Set<t.Identifier>;
677
#hoistedIdentifiers: Set<t.Identifier>;
678
+ parentFunction: NodePath<t.Function>;
679
680
constructor(
681
scope: BabelScope,
@@ -682,6 +683,7 @@ export class Environment {
683
compilerMode: CompilerMode,
684
config: EnvironmentConfig,
685
contextIdentifiers: Set<t.Identifier>,
686
+ parentFunction: NodePath<t.Function>, // the outermost function being compiled
687
logger: Logger | null,
688
filename: string | null,
689
code: string | null,
@@ -740,6 +742,7 @@ export class Environment {
742
this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType);
743
}
744
745
+ this.parentFunction = parentFunction;
746
this.#contextIdentifiers = contextIdentifiers;
747
this.#hoistedIdentifiers = new Set();
748
}
compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts
+17
-11
@@ -110,7 +110,6 @@ export default class HIRBuilder {
110
#bindings: Bindings;
111
#env: Environment;
112
#exceptionHandlerStack: Array<BlockId> = [];
113
- parentFunction: NodePath<t.Function>;
113
errors: CompilerError = new CompilerError();
114
/**
115
* Traversal context: counts the number of `fbt` tag parents
@@ -136,16 +135,17 @@ export default class HIRBuilder {
135
136
constructor(
137
env: Environment,
139
- parentFunction: NodePath<t.Function>, // the outermost function being compiled
140
- bindings: Bindings | null = null,
141
- context: Array<t.Identifier> | null = null,
138
+ options?: {
139
+ bindings?: Bindings | null;
140
+ context?: Array<t.Identifier>;
141
+ entryBlockKind?: BlockKind;
142
+ },
143
) {
144
this.#env = env;
144
- this.#bindings = bindings ?? new Map();
145
- this.parentFunction = parentFunction;
146
- this.#context = context ?? [];
145
+ this.#bindings = options?.bindings ?? new Map();
146
+ this.#context = options?.context ?? [];
147
this.#entry = makeBlockId(env.nextBlockId);
148
- this.#current = newBlock(this.#entry, 'block');
148
+ this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block');
149
}
150
151
currentBlockKind(): BlockKind {
@@ -239,7 +239,7 @@ export default class HIRBuilder {
239
240
// Check if the binding is from module scope
241
const outerBinding =
242
- this.parentFunction.scope.parent.getBinding(originalName);
242
+ this.#env.parentFunction.scope.parent.getBinding(originalName);
243
if (babelBinding === outerBinding) {
244
const path = babelBinding.path;
245
if (path.isImportDefaultSpecifier()) {
@@ -293,7 +293,7 @@ export default class HIRBuilder {
293
const binding = this.#resolveBabelBinding(path);
294
if (binding) {
295
// Check if the binding is from module scope, if so return null
296
- const outerBinding = this.parentFunction.scope.parent.getBinding(
296
+ const outerBinding = this.#env.parentFunction.scope.parent.getBinding(
297
path.node.name,
298
);
299
if (binding === outerBinding) {
@@ -376,7 +376,7 @@ export default class HIRBuilder {
376
}
377
378
// Terminate the current block w the given terminal, and start a new block
379
- terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void {
379
+ terminate(terminal: Terminal, nextBlockKind: BlockKind | null): BlockId {
380
const {id: blockId, kind, instructions} = this.#current;
381
this.#completed.set(blockId, {
382
kind,
@@ -390,6 +390,7 @@ export default class HIRBuilder {
390
const nextId = this.#env.nextBlockId;
391
this.#current = newBlock(nextId, nextBlockKind);
392
}
393
+ return blockId;
394
}
395
396
/*
@@ -746,6 +747,11 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
747
* (eg bb2 then bb1), we ensure that they get reversed back to the correct order.
748
*/
749
const block = func.blocks.get(blockId)!;
750
+ CompilerError.invariant(block != null, {
751
+ reason: '[HIRBuilder] Unexpected null block',
752
+ description: `expected block ${blockId} to exist`,
753
+ loc: GeneratedSource,
754
+ });
755
const successors = [...eachTerminalSuccessor(block.terminal)].reverse();
756
const fallthrough = terminalFallthrough(block.terminal);
757