main
ts 127 lines 3.94 KB
Raw
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 {Effect, HIRFunction, IdentifierId, makeInstructionId} from '../HIR';
10 import {deadCodeElimination} from '../Optimization';
11 import {inferReactiveScopeVariables} from '../ReactiveScopes';
12 import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
13 import {assertExhaustive} from '../Utils/utils';
14 import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
15 import {inferMutationAliasingRanges} from './InferMutationAliasingRanges';
16
17 export default function analyseFunctions(func: HIRFunction): void {
18 for (const [_, block] of func.body.blocks) {
19 for (const instr of block.instructions) {
20 switch (instr.value.kind) {
21 case 'ObjectMethod':
22 case 'FunctionExpression': {
23 lowerWithMutationAliasing(instr.value.loweredFunc.func);
24
25 /**
26 * Reset mutable range for outer inferReferenceEffects
27 */
28 for (const operand of instr.value.loweredFunc.func.context) {
29 /**
30 * NOTE: inferReactiveScopeVariables makes identifiers in the scope
31 * point to the *same* mutableRange instance. Resetting start/end
32 * here is insufficient, because a later mutation of the range
33 * for any one identifier could affect the range for other identifiers.
34 */
35 operand.identifier.mutableRange = {
36 start: makeInstructionId(0),
37 end: makeInstructionId(0),
38 };
39 operand.identifier.scope = null;
40 }
41 break;
42 }
43 }
44 }
45 }
46 }
47
48 function lowerWithMutationAliasing(fn: HIRFunction): void {
49 /**
50 * Phase 1: similar to lower(), but using the new mutation/aliasing inference
51 */
52 analyseFunctions(fn);
53 inferMutationAliasingEffects(fn, {isFunctionExpression: true});
54 deadCodeElimination(fn);
55 const functionEffects = inferMutationAliasingRanges(fn, {
56 isFunctionExpression: true,
57 });
58 rewriteInstructionKindsBasedOnReassignment(fn);
59 inferReactiveScopeVariables(fn);
60 fn.aliasingEffects = functionEffects;
61
62 /**
63 * Phase 2: populate the Effect of each context variable to use in inferring
64 * the outer function. For example, InferMutationAliasingEffects uses context variable
65 * effects to decide if the function may be mutable or not.
66 */
67 const capturedOrMutated = new Set<IdentifierId>();
68 for (const effect of functionEffects) {
69 switch (effect.kind) {
70 case 'Assign':
71 case 'Alias':
72 case 'Capture':
73 case 'CreateFrom':
74 case 'MaybeAlias': {
75 capturedOrMutated.add(effect.from.identifier.id);
76 break;
77 }
78 case 'Apply': {
79 CompilerError.invariant(false, {
80 reason: `[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects`,
81 loc: effect.function.loc,
82 });
83 }
84 case 'Mutate':
85 case 'MutateConditionally':
86 case 'MutateTransitive':
87 case 'MutateTransitiveConditionally': {
88 capturedOrMutated.add(effect.value.identifier.id);
89 break;
90 }
91 case 'Impure':
92 case 'Render':
93 case 'MutateFrozen':
94 case 'MutateGlobal':
95 case 'CreateFunction':
96 case 'Create':
97 case 'Freeze':
98 case 'ImmutableCapture': {
99 // no-op
100 break;
101 }
102 default: {
103 assertExhaustive(
104 effect,
105 `Unexpected effect kind ${(effect as any).kind}`,
106 );
107 }
108 }
109 }
110
111 for (const operand of fn.context) {
112 if (
113 capturedOrMutated.has(operand.identifier.id) ||
114 operand.effect === Effect.Capture
115 ) {
116 operand.effect = Effect.Capture;
117 } else {
118 operand.effect = Effect.Read;
119 }
120 }
121
122 fn.env.logger?.debugLogIRs?.({
123 kind: 'hir',
124 name: 'AnalyseFunction (inner)',
125 value: fn,
126 });
127 }