[compiler] Clean up deadcode: DeriveMinimalDeps (non-hir fork) (#32104)
(title) --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32104). * #32287 * __->__ #32104 * #32098 * #32097
mofeiZ committed
Feb 18, 2025 at 09:38 UTC
19cc5af41ead904100a55fd2a1cfb40b1380f4be
2 files changed
+1
-641
compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts
+1
-2
@@ -13,7 +13,6 @@ import {
13
ReactiveScopeDependency,
14
} from '../HIR';
15
import {printIdentifier} from '../HIR/PrintHIR';
16
-import {ReactiveScopePropertyDependency} from '../ReactiveScopes/DeriveMinimalDependencies';
16
17
/**
18
* Simpler fork of DeriveMinimalDependencies, see PropagateScopeDependenciesHIR
@@ -91,7 +90,7 @@ export class ReactiveScopeDependencyTreeHIR {
90
* dependency. This effectively truncates @param dep to its maximal
91
* safe-to-evaluate subpath
92
*/
94
- addDependency(dep: ReactiveScopePropertyDependency): void {
93
+ addDependency(dep: ReactiveScopeDependency): void {
94
const {identifier, path} = dep;
95
let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
96
identifier,
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts
deleted
-639
@@ -1,639 +0,0 @@
1
-/**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
-import {CompilerError} from '../CompilerError';
9
-import {DependencyPath, Identifier, ReactiveScopeDependency} from '../HIR';
10
-import {printIdentifier} from '../HIR/PrintHIR';
11
-import {assertExhaustive} from '../Utils/utils';
12
-
13
-/*
14
- * We need to understand optional member expressions only when determining
15
- * dependencies of a ReactiveScope (i.e. in {@link PropagateScopeDependencies}),
16
- * hence why this type lives here (not in HIR.ts)
17
- */
18
-export type ReactiveScopePropertyDependency = ReactiveScopeDependency;
19
-
20
-/*
21
- * Finalizes a set of ReactiveScopeDependencies to produce a set of minimal unconditional
22
- * dependencies, preserving granular accesses when possible.
23
- *
24
- * Correctness properties:
25
- * - All dependencies to a ReactiveBlock must be tracked.
26
- * We can always truncate a dependency's path to a subpath, due to Forget assuming
27
- * deep immutability. If the value produced by a subpath has not changed, then
28
- * dependency must have not changed.
29
- * i.e. props.a === $[..] implies props.a.b === $[..]
30
- *
31
- * Note the inverse is not true, but this only means a false positive (we run the
32
- * reactive block more than needed).
33
- * i.e. props.a !== $[..] does not imply props.a.b !== $[..]
34
- *
35
- * - The dependencies of a finalized ReactiveBlock must be all safe to access
36
- * unconditionally (i.e. preserve program semantics with respect to nullthrows).
37
- * If a dependency is only accessed within a conditional, we must track the nearest
38
- * unconditionally accessed subpath instead.
39
- * @param initialDeps
40
- * @returns
41
- */
42
-export class ReactiveScopeDependencyTree {
43
- #roots: Map<Identifier, DependencyNode> = new Map();
44
-
45
- #getOrCreateRoot(identifier: Identifier): DependencyNode {
46
- // roots can always be accessed unconditionally in JS
47
- let rootNode = this.#roots.get(identifier);
48
-
49
- if (rootNode === undefined) {
50
- rootNode = {
51
- properties: new Map(),
52
- accessType: PropertyAccessType.UnconditionalAccess,
53
- };
54
- this.#roots.set(identifier, rootNode);
55
- }
56
- return rootNode;
57
- }
58
-
59
- add(dep: ReactiveScopePropertyDependency, inConditional: boolean): void {
60
- const {path} = dep;
61
- let currNode = this.#getOrCreateRoot(dep.identifier);
62
-
63
- for (const item of path) {
64
- // all properties read 'on the way' to a dependency are marked as 'access'
65
- let currChild = getOrMakeProperty(currNode, item.property);
66
- const accessType = inConditional
67
- ? PropertyAccessType.ConditionalAccess
68
- : item.optional
69
- ? PropertyAccessType.OptionalAccess
70
- : PropertyAccessType.UnconditionalAccess;
71
- currChild.accessType = merge(currChild.accessType, accessType);
72
- currNode = currChild;
73
- }
74
-
75
- /**
76
- * The final property node should be marked as an conditional/unconditional
77
- * `dependency` as based on control flow.
78
- */
79
- const depType = inConditional
80
- ? PropertyAccessType.ConditionalDependency
81
- : isOptional(currNode.accessType)
82
- ? PropertyAccessType.OptionalDependency
83
- : PropertyAccessType.UnconditionalDependency;
84
-
85
- currNode.accessType = merge(currNode.accessType, depType);
86
- }
87
-
88
- deriveMinimalDependencies(): Set<ReactiveScopeDependency> {
89
- const results = new Set<ReactiveScopeDependency>();
90
- for (const [rootId, rootNode] of this.#roots.entries()) {
91
- const deps = deriveMinimalDependenciesInSubtree(rootNode, null);
92
- CompilerError.invariant(
93
- deps.every(
94
- dep =>
95
- dep.accessType === PropertyAccessType.UnconditionalDependency ||
96
- dep.accessType == PropertyAccessType.OptionalDependency,
97
- ),
98
- {
99
- reason:
100
- '[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.',
101
- description: null,
102
- loc: null,
103
- suggestions: null,
104
- },
105
- );
106
-
107
- for (const dep of deps) {
108
- results.add({
109
- identifier: rootId,
110
- path: dep.relativePath,
111
- });
112
- }
113
- }
114
-
115
- return results;
116
- }
117
-
118
- addDepsFromInnerScope(
119
- depsFromInnerScope: ReactiveScopeDependencyTree,
120
- innerScopeInConditionalWithinParent: boolean,
121
- checkValidDepIdFn: (dep: ReactiveScopeDependency) => boolean,
122
- ): void {
123
- for (const [id, otherRoot] of depsFromInnerScope.#roots) {
124
- if (!checkValidDepIdFn({identifier: id, path: []})) {
125
- continue;
126
- }
127
- let currRoot = this.#getOrCreateRoot(id);
128
- addSubtree(currRoot, otherRoot, innerScopeInConditionalWithinParent);
129
- if (!isUnconditional(currRoot.accessType)) {
130
- currRoot.accessType = isDependency(currRoot.accessType)
131
- ? PropertyAccessType.UnconditionalDependency
132
- : PropertyAccessType.UnconditionalAccess;
133
- }
134
- }
135
- }
136
-
137
- promoteDepsFromExhaustiveConditionals(
138
- trees: Array<ReactiveScopeDependencyTree>,
139
- ): void {
140
- CompilerError.invariant(trees.length > 1, {
141
- reason: 'Expected trees to be at least 2 elements long.',
142
- description: null,
143
- loc: null,
144
- suggestions: null,
145
- });
146
-
147
- for (const [id, root] of this.#roots) {
148
- const nodesForRootId = mapNonNull(trees, tree => {
149
- const node = tree.#roots.get(id);
150
- if (node != null && isUnconditional(node.accessType)) {
151
- return node;
152
- } else {
153
- return null;
154
- }
155
- });
156
- if (nodesForRootId) {
157
- addSubtreeIntersection(
158
- root.properties,
159
- nodesForRootId.map(root => root.properties),
160
- );
161
- }
162
- }
163
- }
164
-
165
- /*
166
- * Prints dependency tree to string for debugging.
167
- * @param includeAccesses
168
- * @returns string representation of DependencyTree
169
- */
170
- printDeps(includeAccesses: boolean): string {
171
- let res = [];
172
-
173
- for (const [rootId, rootNode] of this.#roots.entries()) {
174
- const rootResults = printSubtree(rootNode, includeAccesses).map(
175
- result => `${printIdentifier(rootId)}.${result}`,
176
- );
177
- res.push(rootResults);
178
- }
179
- return res.flat().join('\n');
180
- }
181
-
182
- debug(): string {
183
- const buf: Array<string> = [`tree() [`];
184
- for (const [rootId, rootNode] of this.#roots) {
185
- buf.push(`${printIdentifier(rootId)} (${rootNode.accessType}):`);
186
- this.#debugImpl(buf, rootNode, 1);
187
- }
188
- buf.push(']');
189
- return buf.length > 2 ? buf.join('\n') : buf.join('');
190
- }
191
-
192
- #debugImpl(
193
- buf: Array<string>,
194
- node: DependencyNode,
195
- depth: number = 0,
196
- ): void {
197
- for (const [property, childNode] of node.properties) {
198
- buf.push(`${' '.repeat(depth)}.${property} (${childNode.accessType}):`);
199
- this.#debugImpl(buf, childNode, depth + 1);
200
- }
201
- }
202
-}
203
-
204
-/*
205
- * Enum representing the access type of single property on a parent object.
206
- * We distinguish on two independent axes:
207
- * Conditional / Unconditional:
208
- * - whether this property is accessed unconditionally (within the ReactiveBlock)
209
- * Access / Dependency:
210
- * - Access: this property is read on the path of a dependency. We do not
211
- * need to track change variables for accessed properties. Tracking accesses
212
- * helps Forget do more granular dependency tracking.
213
- * - Dependency: this property is read as a dependency and we must track changes
214
- * to it for correctness.
215
- *
216
- * ```javascript
217
- * // props.a is a dependency here and must be tracked
218
- * deps: {props.a, props.a.b} ---> minimalDeps: {props.a}
219
- * // props.a is just an access here and does not need to be tracked
220
- * deps: {props.a.b} ---> minimalDeps: {props.a.b}
221
- * ```
222
- */
223
-enum PropertyAccessType {
224
- ConditionalAccess = 'ConditionalAccess',
225
- OptionalAccess = 'OptionalAccess',
226
- UnconditionalAccess = 'UnconditionalAccess',
227
- ConditionalDependency = 'ConditionalDependency',
228
- OptionalDependency = 'OptionalDependency',
229
- UnconditionalDependency = 'UnconditionalDependency',
230
-}
231
-
232
-const MIN_ACCESS_TYPE = PropertyAccessType.ConditionalAccess;
233
-function isUnconditional(access: PropertyAccessType): boolean {
234
- return (
235
- access === PropertyAccessType.UnconditionalAccess ||
236
- access === PropertyAccessType.UnconditionalDependency
237
- );
238
-}
239
-function isDependency(access: PropertyAccessType): boolean {
240
- return (
241
- access === PropertyAccessType.ConditionalDependency ||
242
- access === PropertyAccessType.OptionalDependency ||
243
- access === PropertyAccessType.UnconditionalDependency
244
- );
245
-}
246
-function isOptional(access: PropertyAccessType): boolean {
247
- return (
248
- access === PropertyAccessType.OptionalAccess ||
249
- access === PropertyAccessType.OptionalDependency
250
- );
251
-}
252
-
253
-function merge(
254
- access1: PropertyAccessType,
255
- access2: PropertyAccessType,
256
-): PropertyAccessType {
257
- const resultIsUnconditional =
258
- isUnconditional(access1) || isUnconditional(access2);
259
- const resultIsDependency = isDependency(access1) || isDependency(access2);
260
- const resultIsOptional = isOptional(access1) || isOptional(access2);
261
-
262
- /*
263
- * Straightforward merge.
264
- * This can be represented as bitwise OR, but is written out for readability
265
- *
266
- * Observe that `UnconditionalAccess | ConditionalDependency` produces an
267
- * unconditionally accessed conditional dependency. We currently use these
268
- * as we use unconditional dependencies. (i.e. to codegen change variables)
269
- */
270
- if (resultIsUnconditional) {
271
- if (resultIsDependency) {
272
- return PropertyAccessType.UnconditionalDependency;
273
- } else {
274
- return PropertyAccessType.UnconditionalAccess;
275
- }
276
- } else if (resultIsOptional) {
277
- if (resultIsDependency) {
278
- return PropertyAccessType.OptionalDependency;
279
- } else {
280
- return PropertyAccessType.OptionalAccess;
281
- }
282
- } else {
283
- if (resultIsDependency) {
284
- return PropertyAccessType.ConditionalDependency;
285
- } else {
286
- return PropertyAccessType.ConditionalAccess;
287
- }
288
- }
289
-}
290
-
291
-type DependencyNode = {
292
- properties: Map<string, DependencyNode>;
293
- accessType: PropertyAccessType;
294
-};
295
-
296
-type ReduceResultNode = {
297
- relativePath: DependencyPath;
298
- accessType: PropertyAccessType;
299
-};
300
-
301
-function promoteResult(
302
- accessType: PropertyAccessType,
303
- path: {property: string; optional: boolean} | null,
304
-): Array<ReduceResultNode> {
305
- const result: ReduceResultNode = {
306
- relativePath: [],
307
- accessType,
308
- };
309
- if (path !== null) {
310
- result.relativePath.push(path);
311
- }
312
- return [result];
313
-}
314
-
315
-function prependPath(
316
- results: Array<ReduceResultNode>,
317
- path: {property: string; optional: boolean} | null,
318
-): Array<ReduceResultNode> {
319
- if (path === null) {
320
- return results;
321
- }
322
- return results.map(result => {
323
- return {
324
- accessType: result.accessType,
325
- relativePath: [path, ...result.relativePath],
326
- };
327
- });
328
-}
329
-
330
-/*
331
- * Recursively calculates minimal dependencies in a subtree.
332
- * @param dep DependencyNode representing a dependency subtree.
333
- * @returns a minimal list of dependencies in this subtree.
334
- */
335
-function deriveMinimalDependenciesInSubtree(
336
- dep: DependencyNode,
337
- property: string | null,
338
-): Array<ReduceResultNode> {
339
- const results: Array<ReduceResultNode> = [];
340
- for (const [childName, childNode] of dep.properties) {
341
- const childResult = deriveMinimalDependenciesInSubtree(
342
- childNode,
343
- childName,
344
- );
345
- results.push(...childResult);
346
- }
347
-
348
- switch (dep.accessType) {
349
- case PropertyAccessType.UnconditionalDependency: {
350
- return promoteResult(
351
- PropertyAccessType.UnconditionalDependency,
352
- property !== null ? {property, optional: false} : null,
353
- );
354
- }
355
- case PropertyAccessType.UnconditionalAccess: {
356
- if (
357
- results.every(
358
- ({accessType}) =>
359
- accessType === PropertyAccessType.UnconditionalDependency ||
360
- accessType === PropertyAccessType.OptionalDependency,
361
- )
362
- ) {
363
- // all children are unconditional dependencies, return them to preserve granularity
364
- return prependPath(
365
- results,
366
- property !== null ? {property, optional: false} : null,
367
- );
368
- } else {
369
- /*
370
- * at least one child is accessed conditionally, so this node needs to be promoted to
371
- * unconditional dependency
372
- */
373
- return promoteResult(
374
- PropertyAccessType.UnconditionalDependency,
375
- property !== null ? {property, optional: false} : null,
376
- );
377
- }
378
- }
379
- case PropertyAccessType.OptionalDependency: {
380
- return promoteResult(
381
- PropertyAccessType.OptionalDependency,
382
- property !== null ? {property, optional: true} : null,
383
- );
384
- }
385
- case PropertyAccessType.OptionalAccess: {
386
- if (
387
- results.every(
388
- ({accessType}) =>
389
- accessType === PropertyAccessType.UnconditionalDependency ||
390
- accessType === PropertyAccessType.OptionalDependency,
391
- )
392
- ) {
393
- // all children are unconditional dependencies, return them to preserve granularity
394
- return prependPath(
395
- results,
396
- property !== null ? {property, optional: true} : null,
397
- );
398
- } else {
399
- /*
400
- * at least one child is accessed conditionally, so this node needs to be promoted to
401
- * unconditional dependency
402
- */
403
- return promoteResult(
404
- PropertyAccessType.OptionalDependency,
405
- property !== null ? {property, optional: true} : null,
406
- );
407
- }
408
- }
409
- case PropertyAccessType.ConditionalAccess:
410
- case PropertyAccessType.ConditionalDependency: {
411
- if (
412
- results.every(
413
- ({accessType}) =>
414
- accessType === PropertyAccessType.ConditionalDependency,
415
- )
416
- ) {
417
- /*
418
- * No children are accessed unconditionally, so we cannot promote this node to
419
- * unconditional access.
420
- * Truncate results of child nodes here, since we shouldn't access them anyways
421
- */
422
- return promoteResult(
423
- PropertyAccessType.ConditionalDependency,
424
- property !== null ? {property, optional: true} : null,
425
- );
426
- } else {
427
- /*
428
- * at least one child is accessed unconditionally, so this node can be promoted to
429
- * unconditional dependency
430
- */
431
- return promoteResult(
432
- PropertyAccessType.UnconditionalDependency,
433
- property !== null ? {property, optional: true} : null,
434
- );
435
- }
436
- }
437
- default: {
438
- assertExhaustive(
439
- dep.accessType,
440
- '[PropgateScopeDependencies] Unhandled access type!',
441
- );
442
- }
443
- }
444
-}
445
-
446
-/*
447
- * Demote all unconditional accesses + dependencies in subtree to the
448
- * conditional equivalent, mutating subtree in place.
449
- * @param subtree unconditional node representing a subtree of dependencies
450
- */
451
-function demoteSubtreeToConditional(subtree: DependencyNode): void {
452
- const stack: Array<DependencyNode> = [subtree];
453
-
454
- let node;
455
- while ((node = stack.pop()) !== undefined) {
456
- const {accessType, properties} = node;
457
- if (!isUnconditional(accessType)) {
458
- // A conditionally accessed node should not have unconditional children
459
- continue;
460
- }
461
- node.accessType = isDependency(accessType)
462
- ? PropertyAccessType.ConditionalDependency
463
- : PropertyAccessType.ConditionalAccess;
464
-
465
- for (const childNode of properties.values()) {
466
- if (isUnconditional(accessType)) {
467
- /*
468
- * No conditional node can have an unconditional node as a child, so
469
- * we only process childNode if it is unconditional
470
- */
471
- stack.push(childNode);
472
- }
473
- }
474
- }
475
-}
476
-
477
-/*
478
- * Calculates currNode = union(currNode, otherNode), mutating currNode in place
479
- * If demoteOtherNode is specified, we demote the subtree represented by
480
- * otherNode to conditional access/deps before taking the union.
481
- *
482
- * This is a helper function used to join an inner scope to its parent scope.
483
- * @param currNode (mutable) return by argument
484
- * @param otherNode (move) {@link addSubtree} takes ownership of the subtree
485
- * represented by otherNode, which may be mutated or moved to currNode. It is
486
- * invalid to use otherNode after this call.
487
- *
488
- * Note that @param otherNode may contain both conditional and unconditional nodes,
489
- * due to inner control flow and conditional member expressions
490
- *
491
- * @param demoteOtherNode
492
- */
493
-function addSubtree(
494
- currNode: DependencyNode,
495
- otherNode: DependencyNode,
496
- demoteOtherNode: boolean,
497
-): void {
498
- let otherType = otherNode.accessType;
499
- if (demoteOtherNode) {
500
- otherType = isDependency(otherType)
501
- ? PropertyAccessType.ConditionalDependency
502
- : PropertyAccessType.ConditionalAccess;
503
- }
504
- currNode.accessType = merge(currNode.accessType, otherType);
505
-
506
- for (const [propertyName, otherChild] of otherNode.properties) {
507
- const currChild = currNode.properties.get(propertyName);
508
- if (currChild) {
509
- // recursively calculate currChild = union(currChild, otherChild)
510
- addSubtree(currChild, otherChild, demoteOtherNode);
511
- } else {
512
- /*
513
- * if currChild doesn't exist, we can just move otherChild
514
- * currChild = otherChild.
515
- */
516
- if (demoteOtherNode) {
517
- demoteSubtreeToConditional(otherChild);
518
- }
519
- currNode.properties.set(propertyName, otherChild);
520
- }
521
- }
522
-}
523
-
524
-/*
525
- * Adds intersection(otherProperties) to currProperties, mutating
526
- * currProperties in place. i.e.
527
- * currProperties = union(currProperties, intersection(otherProperties))
528
- *
529
- * Used to merge unconditional accesses from exhaustive conditional branches
530
- * into the parent ReactiveDeps Tree.
531
- * intersection(currProperties) is determined as such:
532
- * - a node is present in the intersection iff it is present in all every
533
- * branch
534
- * - the type of an added node is `UnconditionalDependency` if it is a
535
- * dependency in at least one branch (otherwise `UnconditionalAccess`)
536
- *
537
- * @param otherProperties (read-only) an array of node properties containing
538
- * conditionally and unconditionally accessed nodes. Each element
539
- * represents asubtree of reactive dependencies from a single CFG
540
- * branch.
541
- * otherProperties must represent all reachable branches.
542
- * @param currProperties (mutable) return by argument properties of a node
543
- *
544
- * otherProperties and currProperties must be properties of disjoint nodes
545
- * that represent the same dependency (identifier + path).
546
- */
547
-function addSubtreeIntersection(
548
- currProperties: Map<string, DependencyNode>,
549
- otherProperties: Array<Map<string, DependencyNode>>,
550
-): void {
551
- CompilerError.invariant(otherProperties.length > 1, {
552
- reason:
553
- '[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.',
554
- description: null,
555
- loc: null,
556
- suggestions: null,
557
- });
558
-
559
- /*
560
- * otherProperties here may contain unconditional nodes as the result of
561
- * recursively merging exhaustively conditional children with unconditionally
562
- * accessed nodes (e.g. in the test condition itself)
563
- * See `reduce-reactive-cond-deps-cfg-nested-testifelse` fixture for example
564
- */
565
-
566
- for (const [propertyName, currNode] of currProperties) {
567
- const otherNodes = mapNonNull(otherProperties, properties => {
568
- const node = properties.get(propertyName);
569
- if (node != null && isUnconditional(node.accessType)) {
570
- return node;
571
- } else {
572
- return null;
573
- }
574
- });
575
-
576
- /*
577
- * intersection(otherNodes[propertyName]) only exists if each element in
578
- * otherProperties accesses propertyName.
579
- */
580
- if (otherNodes) {
581
- addSubtreeIntersection(
582
- currNode.properties,
583
- otherNodes.map(node => node.properties),
584
- );
585
-
586
- const isDep = otherNodes.some(tree => isDependency(tree.accessType));
587
- const externalAccessType = isDep
588
- ? PropertyAccessType.UnconditionalDependency
589
- : PropertyAccessType.UnconditionalAccess;
590
- currNode.accessType = merge(externalAccessType, currNode.accessType);
591
- }
592
- }
593
-}
594
-
595
-function printSubtree(
596
- node: DependencyNode,
597
- includeAccesses: boolean,
598
-): Array<string> {
599
- const results: Array<string> = [];
600
- for (const [propertyName, propertyNode] of node.properties) {
601
- if (includeAccesses || isDependency(propertyNode.accessType)) {
602
- results.push(`${propertyName} (${propertyNode.accessType})`);
603
- }
604
- const propertyResults = printSubtree(propertyNode, includeAccesses);
605
- results.push(...propertyResults.map(result => `${propertyName}.${result}`));
606
- }
607
- return results;
608
-}
609
-
610
-function getOrMakeProperty(
611
- node: DependencyNode,
612
- property: string,
613
-): DependencyNode {
614
- let child = node.properties.get(property);
615
- if (child == null) {
616
- child = {
617
- properties: new Map(),
618
- accessType: MIN_ACCESS_TYPE,
619
- };
620
- node.properties.set(property, child);
621
- }
622
- return child;
623
-}
624
-
625
-function mapNonNull<T extends NonNullable<V>, V, U>(
626
- arr: Array<U>,
627
- fn: (arg0: U) => T | undefined | null,
628
-): Array<T> | null {
629
- const result = [];
630
- for (let i = 0; i < arr.length; i++) {
631
- const element = fn(arr[i]);
632
- if (element) {
633
- result.push(element);
634
- } else {
635
- return null;
636
- }
637
- }
638
- return result;
639
-}