@samitouri / QOS-React / commits / 19cc5af41e

[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 -}