main
ts 168 lines 6.25 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 {
10 DeclarationId,
11 GeneratedSource,
12 HIRFunction,
13 InstructionKind,
14 LValue,
15 LValuePattern,
16 Place,
17 } from '../HIR/HIR';
18 import {printPlace} from '../HIR/PrintHIR';
19 import {eachPatternOperand} from '../HIR/visitors';
20
21 /**
22 * This pass rewrites the InstructionKind of instructions which declare/assign variables,
23 * converting the first declaration to a Const/Let depending on whether it is subsequently
24 * reassigned, and ensuring that subsequent reassignments are marked as a Reassign. Note
25 * that declarations which were const in the original program cannot become `let`, but the
26 * inverse is not true: a `let` which was reassigned in the source may be converted to a
27 * `const` if the reassignment is not used and was removed by dead code elimination.
28 *
29 * NOTE: this is a subset of the operations previously performed by the LeaveSSA pass.
30 */
31 export function rewriteInstructionKindsBasedOnReassignment(
32 fn: HIRFunction,
33 ): void {
34 const declarations = new Map<DeclarationId, LValue | LValuePattern>();
35 for (const param of fn.params) {
36 let place: Place = param.kind === 'Identifier' ? param : param.place;
37 if (place.identifier.name !== null) {
38 declarations.set(place.identifier.declarationId, {
39 kind: InstructionKind.Let,
40 place,
41 });
42 }
43 }
44 for (const place of fn.context) {
45 if (place.identifier.name !== null) {
46 declarations.set(place.identifier.declarationId, {
47 kind: InstructionKind.Let,
48 place,
49 });
50 }
51 }
52 for (const [, block] of fn.body.blocks) {
53 for (const instr of block.instructions) {
54 const {value} = instr;
55 switch (value.kind) {
56 case 'DeclareLocal': {
57 const lvalue = value.lvalue;
58 CompilerError.invariant(
59 !declarations.has(lvalue.place.identifier.declarationId),
60 {
61 reason: `Expected variable not to be defined prior to declaration`,
62 description: `${printPlace(lvalue.place)} was already defined`,
63 loc: lvalue.place.loc,
64 },
65 );
66 declarations.set(lvalue.place.identifier.declarationId, lvalue);
67 break;
68 }
69 case 'StoreLocal': {
70 const lvalue = value.lvalue;
71 if (lvalue.place.identifier.name !== null) {
72 const declaration = declarations.get(
73 lvalue.place.identifier.declarationId,
74 );
75 if (declaration === undefined) {
76 CompilerError.invariant(
77 !declarations.has(lvalue.place.identifier.declarationId),
78 {
79 reason: `Expected variable not to be defined prior to declaration`,
80 description: `${printPlace(lvalue.place)} was already defined`,
81 loc: lvalue.place.loc,
82 },
83 );
84 declarations.set(lvalue.place.identifier.declarationId, lvalue);
85 lvalue.kind = InstructionKind.Const;
86 } else {
87 declaration.kind = InstructionKind.Let;
88 lvalue.kind = InstructionKind.Reassign;
89 }
90 }
91 break;
92 }
93 case 'Destructure': {
94 const lvalue = value.lvalue;
95 let kind: InstructionKind | null = null;
96 for (const place of eachPatternOperand(lvalue.pattern)) {
97 if (place.identifier.name === null) {
98 CompilerError.invariant(
99 kind === null || kind === InstructionKind.Const,
100 {
101 reason: `Expected consistent kind for destructuring`,
102 description: `other places were \`${kind}\` but '${printPlace(
103 place,
104 )}' is const`,
105 loc: place.loc,
106 },
107 );
108 kind = InstructionKind.Const;
109 } else {
110 const declaration = declarations.get(
111 place.identifier.declarationId,
112 );
113 if (declaration === undefined) {
114 CompilerError.invariant(block.kind !== 'value', {
115 reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
116 loc: place.loc,
117 });
118 declarations.set(place.identifier.declarationId, lvalue);
119 CompilerError.invariant(
120 kind === null || kind === InstructionKind.Const,
121 {
122 reason: `Expected consistent kind for destructuring`,
123 description: `Other places were \`${kind}\` but '${printPlace(
124 place,
125 )}' is const`,
126 loc: place.loc,
127 },
128 );
129 kind = InstructionKind.Const;
130 } else {
131 CompilerError.invariant(
132 kind === null || kind === InstructionKind.Reassign,
133 {
134 reason: `Expected consistent kind for destructuring`,
135 description: `Other places were \`${kind}\` but '${printPlace(
136 place,
137 )}' is reassigned`,
138 loc: place.loc,
139 },
140 );
141 kind = InstructionKind.Reassign;
142 declaration.kind = InstructionKind.Let;
143 }
144 }
145 }
146 CompilerError.invariant(kind !== null, {
147 reason: 'Expected at least one operand',
148 loc: GeneratedSource,
149 });
150 lvalue.kind = kind;
151 break;
152 }
153 case 'PostfixUpdate':
154 case 'PrefixUpdate': {
155 const lvalue = value.lvalue;
156 const declaration = declarations.get(lvalue.identifier.declarationId);
157 CompilerError.invariant(declaration !== undefined, {
158 reason: `Expected variable to have been defined`,
159 description: `No declaration for ${printPlace(lvalue)}`,
160 loc: lvalue.loc,
161 });
162 declaration.kind = InstructionKind.Let;
163 break;
164 }
165 }
166 }
167 }
168 }