Skip to content

Commit 7e2d348

Browse files
committed
[compiler][rewrite] Represent scope dependencies with value blocks
(needs cleanup) - Scopes no longer store a flat list of their dependencies. Instead: - Scope terminals are effectively a `goto` for scope dependency instructions (represented as value blocks that terminate with a `goto scopeBlock` for HIR and a series of ReactiveInstructions for ReactiveIR) - Scopes themselves store `dependencies: Array<Place>`, which refer to temporaries written to by scope dependency instructions Next steps: - new pass to dedupe scope dependency instructions after all dependency and scope pruning passes, effectively 'hoisting' dependencies out - more complex dependencies (unary ops like `Boolean` or `Not`, binary ops like `!==` or logical operators)
1 parent 5ff9904 commit 7e2d348

34 files changed

+1348
-803
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ export function lower(
7373
// the outermost function being compiled, in case lower() is called recursively (for lambdas)
7474
parent: NodePath<t.Function> | null = null,
7575
): Result<HIRFunction, CompilerError> {
76-
const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs);
76+
const builder = new HIRBuilder(env, parent ?? func, {
77+
bindings,
78+
context: capturedRefs,
79+
});
7780
const context: HIRFunction['context'] = [];
7881

7982
for (const ref of capturedRefs ?? []) {
@@ -3414,7 +3417,7 @@ function lowerExpressionToTemporary(
34143417
return lowerValueToTemporary(builder, value);
34153418
}
34163419

3417-
function lowerValueToTemporary(
3420+
export function lowerValueToTemporary(
34183421
builder: HIRBuilder,
34193422
value: InstructionValue,
34203423
): Place {

compiler/packages/babel-plugin-react-compiler/src/HIR/BuildReactiveScopeTerminalsHIR.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ type TerminalRewriteInfo =
184184
| {
185185
kind: 'StartScope';
186186
blockId: BlockId;
187+
dependencyId: BlockId;
187188
fallthroughId: BlockId;
188189
instrId: InstructionId;
189190
scope: ReactiveScope;
@@ -208,12 +209,14 @@ function pushStartScopeTerminal(
208209
scope: ReactiveScope,
209210
context: ScopeTraversalContext,
210211
): void {
212+
const dependencyId = context.env.nextBlockId;
211213
const blockId = context.env.nextBlockId;
212214
const fallthroughId = context.env.nextBlockId;
213215
context.rewrites.push({
214216
kind: 'StartScope',
215217
blockId,
216218
fallthroughId,
219+
dependencyId,
217220
instrId: scope.range.start,
218221
scope,
219222
});
@@ -255,10 +258,13 @@ type RewriteContext = {
255258
* instr1, instr2, instr3, instr4, [[ original terminal ]]
256259
* Rewritten:
257260
* bb0:
258-
* instr1, [[ scope start block=bb1]]
261+
* instr1, [[ scope start dependencies=bb1 block=bb2]]
259262
* bb1:
260-
* instr2, instr3, [[ scope end goto=bb2 ]]
263+
* [[ empty, filled in in PropagateScopeDependenciesHIR ]]
264+
* goto bb2
261265
* bb2:
266+
* instr2, instr3, [[ scope end goto=bb3 ]]
267+
* bb3:
262268
* instr4, [[ original terminal ]]
263269
*/
264270
function handleRewrite(
@@ -272,6 +278,7 @@ function handleRewrite(
272278
? {
273279
kind: 'scope',
274280
fallthrough: terminalInfo.fallthroughId,
281+
dependencies: terminalInfo.dependencyId,
275282
block: terminalInfo.blockId,
276283
scope: terminalInfo.scope,
277284
id: terminalInfo.instrId,
@@ -298,7 +305,28 @@ function handleRewrite(
298305
context.nextPreds = new Set([currBlockId]);
299306
context.nextBlockId =
300307
terminalInfo.kind === 'StartScope'
301-
? terminalInfo.blockId
308+
? terminalInfo.dependencyId
302309
: terminalInfo.fallthroughId;
303310
context.instrSliceIdx = idx;
311+
312+
if (terminalInfo.kind === 'StartScope') {
313+
const currBlockId = context.nextBlockId;
314+
context.rewrites.push({
315+
kind: context.source.kind,
316+
id: currBlockId,
317+
instructions: [],
318+
preds: context.nextPreds,
319+
// Only the first rewrite should reuse source block phis
320+
phis: new Set(),
321+
terminal: {
322+
kind: 'goto',
323+
variant: GotoVariant.Break,
324+
block: terminal.block,
325+
id: terminalInfo.instrId,
326+
loc: GeneratedSource,
327+
},
328+
});
329+
context.nextPreds = new Set([currBlockId]);
330+
context.nextBlockId = terminalInfo.blockId;
331+
}
304332
}

compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {react} from '@babel/types';
12
import {CompilerError} from '../CompilerError';
23
import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables';
34
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
@@ -194,7 +195,10 @@ type PropertyPathNode =
194195
class PropertyPathRegistry {
195196
roots: Map<IdentifierId, RootNode> = new Map();
196197

197-
getOrCreateIdentifier(identifier: Identifier): PropertyPathNode {
198+
getOrCreateIdentifier(
199+
identifier: Identifier,
200+
reactive: boolean,
201+
): PropertyPathNode {
198202
/**
199203
* Reads from a statically scoped variable are always safe in JS,
200204
* with the exception of TDZ (not addressed by this pass).
@@ -208,12 +212,18 @@ class PropertyPathRegistry {
208212
optionalProperties: new Map(),
209213
fullPath: {
210214
identifier,
215+
reactive,
211216
path: [],
212217
},
213218
hasOptional: false,
214219
parent: null,
215220
};
216221
this.roots.set(identifier.id, rootNode);
222+
} else {
223+
CompilerError.invariant(reactive === rootNode.fullPath.reactive, {
224+
reason: 'Inconsistent reactive flag',
225+
loc: GeneratedSource,
226+
});
217227
}
218228
return rootNode;
219229
}
@@ -231,6 +241,7 @@ class PropertyPathRegistry {
231241
parent: parent,
232242
fullPath: {
233243
identifier: parent.fullPath.identifier,
244+
reactive: parent.fullPath.reactive,
234245
path: parent.fullPath.path.concat(entry),
235246
},
236247
hasOptional: parent.hasOptional || entry.optional,
@@ -246,7 +257,7 @@ class PropertyPathRegistry {
246257
* so all subpaths of a PropertyLoad should already exist
247258
* (e.g. a.b is added before a.b.c),
248259
*/
249-
let currNode = this.getOrCreateIdentifier(n.identifier);
260+
let currNode = this.getOrCreateIdentifier(n.identifier, n.reactive);
250261
if (n.path.length === 0) {
251262
return currNode;
252263
}
@@ -268,10 +279,11 @@ function getMaybeNonNullInInstruction(
268279
instr: InstructionValue,
269280
context: CollectHoistablePropertyLoadsContext,
270281
): PropertyPathNode | null {
271-
let path = null;
282+
let path: ReactiveScopeDependency | null = null;
272283
if (instr.kind === 'PropertyLoad') {
273284
path = context.temporaries.get(instr.object.identifier.id) ?? {
274285
identifier: instr.object.identifier,
286+
reactive: instr.object.reactive,
275287
path: [],
276288
};
277289
} else if (instr.kind === 'Destructure') {
@@ -334,7 +346,7 @@ function collectNonNullsInBlocks(
334346
) {
335347
const identifier = fn.params[0].identifier;
336348
knownNonNullIdentifiers.add(
337-
context.registry.getOrCreateIdentifier(identifier),
349+
context.registry.getOrCreateIdentifier(identifier, true),
338350
);
339351
}
340352
const nodes = new Map<BlockId, BlockInfo>();
@@ -565,9 +577,11 @@ function reduceMaybeOptionalChains(
565577
changed = false;
566578

567579
for (const original of optionalChainNodes) {
568-
let {identifier, path: origPath} = original.fullPath;
569-
let currNode: PropertyPathNode =
570-
registry.getOrCreateIdentifier(identifier);
580+
let {identifier, path: origPath, reactive} = original.fullPath;
581+
let currNode: PropertyPathNode = registry.getOrCreateIdentifier(
582+
identifier,
583+
reactive,
584+
);
571585
for (let i = 0; i < origPath.length; i++) {
572586
const entry = origPath[i];
573587
// If the base is known to be non-null, replace with a non-optional load

compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export type OptionalChainSidemap = {
106106
hoistableObjects: ReadonlyMap<BlockId, ReactiveScopeDependency>;
107107
};
108108

109-
type OptionalTraversalContext = {
109+
export type OptionalTraversalContext = {
110110
currFn: HIRFunction;
111111
blocks: ReadonlyMap<BlockId, BasicBlock>;
112112

@@ -227,7 +227,7 @@ function matchOptionalTestBlock(
227227
* property loads. If any part of the optional chain is not hoistable, returns
228228
* null.
229229
*/
230-
function traverseOptionalBlock(
230+
export function traverseOptionalBlock(
231231
optional: TBasicBlock<OptionalTerminal>,
232232
context: OptionalTraversalContext,
233233
outerAlternate: BlockId | null,
@@ -282,6 +282,7 @@ function traverseOptionalBlock(
282282
);
283283
baseObject = {
284284
identifier: maybeTest.instructions[0].value.place.identifier,
285+
reactive: maybeTest.instructions[0].value.place.reactive,
285286
path,
286287
};
287288
test = maybeTest.terminal;
@@ -383,6 +384,7 @@ function traverseOptionalBlock(
383384
);
384385
const load = {
385386
identifier: baseObject.identifier,
387+
reactive: baseObject.reactive,
386388
path: [
387389
...baseObject.path,
388390
{

compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
ReactiveScopeDependency,
1414
} from '../HIR';
1515
import {printIdentifier} from '../HIR/PrintHIR';
16-
import {ReactiveScopePropertyDependency} from '../ReactiveScopes/DeriveMinimalDependencies';
16+
17+
export type ReactiveScopePropertyDependency = ReactiveScopeDependency;
1718

1819
/**
1920
* Simpler fork of DeriveMinimalDependencies, see PropagateScopeDependenciesHIR
@@ -25,8 +26,9 @@ export class ReactiveScopeDependencyTreeHIR {
2526
* `identifier.path`, or `identifier?.path` is in this map, it is safe to
2627
* evaluate (non-optional) PropertyLoads from.
2728
*/
28-
#hoistableObjects: Map<Identifier, HoistableNode> = new Map();
29-
#deps: Map<Identifier, DependencyNode> = new Map();
29+
#hoistableObjects: Map<Identifier, HoistableNode & {reactive: boolean}> =
30+
new Map();
31+
#deps: Map<Identifier, DependencyNode & {reactive: boolean}> = new Map();
3032

3133
/**
3234
* @param hoistableObjects a set of paths from which we can safely evaluate
@@ -35,9 +37,10 @@ export class ReactiveScopeDependencyTreeHIR {
3537
* duplicates when traversing the CFG.
3638
*/
3739
constructor(hoistableObjects: Iterable<ReactiveScopeDependency>) {
38-
for (const {path, identifier} of hoistableObjects) {
40+
for (const {path, identifier, reactive} of hoistableObjects) {
3941
let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
4042
identifier,
43+
reactive,
4144
this.#hoistableObjects,
4245
path.length > 0 && path[0].optional ? 'Optional' : 'NonNull',
4346
);
@@ -70,7 +73,8 @@ export class ReactiveScopeDependencyTreeHIR {
7073

7174
static #getOrCreateRoot<T extends string>(
7275
identifier: Identifier,
73-
roots: Map<Identifier, TreeNode<T>>,
76+
reactive: boolean,
77+
roots: Map<Identifier, TreeNode<T> & {reactive: boolean}>,
7478
defaultAccessType: T,
7579
): TreeNode<T> {
7680
// roots can always be accessed unconditionally in JS
@@ -79,9 +83,16 @@ export class ReactiveScopeDependencyTreeHIR {
7983
if (rootNode === undefined) {
8084
rootNode = {
8185
properties: new Map(),
86+
reactive,
8287
accessType: defaultAccessType,
8388
};
8489
roots.set(identifier, rootNode);
90+
} else {
91+
CompilerError.invariant(reactive === rootNode.reactive, {
92+
reason: 'Conflicting reactive status',
93+
description: `Identifier ${printIdentifier(identifier)}`,
94+
loc: GeneratedSource,
95+
});
8596
}
8697
return rootNode;
8798
}
@@ -92,9 +103,10 @@ export class ReactiveScopeDependencyTreeHIR {
92103
* safe-to-evaluate subpath
93104
*/
94105
addDependency(dep: ReactiveScopePropertyDependency): void {
95-
const {identifier, path} = dep;
106+
const {identifier, reactive, path} = dep;
96107
let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
97108
identifier,
109+
reactive,
98110
this.#deps,
99111
PropertyAccessType.UnconditionalAccess,
100112
);
@@ -172,7 +184,13 @@ export class ReactiveScopeDependencyTreeHIR {
172184
deriveMinimalDependencies(): Set<ReactiveScopeDependency> {
173185
const results = new Set<ReactiveScopeDependency>();
174186
for (const [rootId, rootNode] of this.#deps.entries()) {
175-
collectMinimalDependenciesInSubtree(rootNode, rootId, [], results);
187+
collectMinimalDependenciesInSubtree(
188+
rootNode,
189+
rootNode.reactive,
190+
rootId,
191+
[],
192+
results,
193+
);
176194
}
177195

178196
return results;
@@ -303,16 +321,18 @@ type DependencyNode = TreeNode<PropertyAccessType>;
303321
*/
304322
function collectMinimalDependenciesInSubtree(
305323
node: DependencyNode,
324+
reactive: boolean,
306325
rootIdentifier: Identifier,
307326
path: Array<DependencyPathEntry>,
308327
results: Set<ReactiveScopeDependency>,
309328
): void {
310329
if (isDependency(node.accessType)) {
311-
results.add({identifier: rootIdentifier, path});
330+
results.add({identifier: rootIdentifier, reactive, path});
312331
} else {
313332
for (const [childName, childNode] of node.properties) {
314333
collectMinimalDependenciesInSubtree(
315334
childNode,
335+
reactive,
316336
rootIdentifier,
317337
[
318338
...path,

0 commit comments

Comments
 (0)