Constant propagation converts computed access to static property where possible
Joe Savona committed
Feb 17, 2023 at 12:32 UTC
fcdcd6038fd72c3a6a5c183f367446fd711ba2b0
5 files changed
+141
-25
compiler/forget/src/Optimization/ConstantPropagation.ts
+97
-23
@@ -5,10 +5,13 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
+import { isValidIdentifier } from "@babel/types";
9
+import invariant from "invariant";
10
import {
11
GotoVariant,
12
HIRFunction,
13
IdentifierId,
14
+ Instruction,
15
InstructionValue,
16
markInstructionIds,
17
markPredecessors,
@@ -97,7 +100,7 @@ function applyConstantPropagation(fn: HIRFunction): boolean {
100
}
101
102
for (const instr of block.instructions) {
100
- const value = evaluateInstruction(constants, instr.value);
103
+ const value = evaluateInstruction(constants, instr);
104
if (value !== null) {
105
instr.value = value;
106
constants.set(instr.lvalue.place.identifier.id, value);
@@ -137,78 +140,149 @@ function applyConstantPropagation(fn: HIRFunction): boolean {
140
141
function evaluateInstruction(
142
constants: Constants,
140
- instr: InstructionValue
143
+ instr: Instruction
144
): Constant | null {
142
- switch (instr.kind) {
145
+ const value = instr.value;
146
+ switch (value.kind) {
147
case "Primitive": {
144
- return instr;
148
+ return value;
149
+ }
150
+ case "ComputedLoad": {
151
+ const property = read(constants, value.property);
152
+ if (
153
+ property !== null &&
154
+ typeof property.value === "string" &&
155
+ isValidIdentifier(property.value)
156
+ ) {
157
+ const nextValue: InstructionValue = {
158
+ kind: "PropertyLoad",
159
+ loc: value.loc,
160
+ property: property.value,
161
+ object: value.object,
162
+ optional: false,
163
+ };
164
+ // Future-proofing: when we add support for optional computed properties,
165
+ // we'll need to copy the value here
166
+ if ((value as any).optional) {
167
+ invariant(
168
+ false,
169
+ "TODO: translate optional computed load to optional property load"
170
+ );
171
+ }
172
+ instr.value = nextValue;
173
+ }
174
+ return null;
175
+ }
176
+ case "ComputedStore": {
177
+ const property = read(constants, value.property);
178
+ if (
179
+ property !== null &&
180
+ typeof property.value === "string" &&
181
+ isValidIdentifier(property.value)
182
+ ) {
183
+ const nextValue: InstructionValue = {
184
+ kind: "PropertyStore",
185
+ loc: value.loc,
186
+ property: property.value,
187
+ object: value.object,
188
+ value: value.value,
189
+ };
190
+ instr.value = nextValue;
191
+ }
192
+ return null;
193
+ }
194
+ case "ComputedCall": {
195
+ const property = read(constants, value.property);
196
+ if (
197
+ property !== null &&
198
+ typeof property.value === "string" &&
199
+ isValidIdentifier(property.value)
200
+ ) {
201
+ const nextValue: InstructionValue = {
202
+ kind: "PropertyCall",
203
+ args: value.args,
204
+ loc: value.loc,
205
+ property: property.value,
206
+ receiver: value.receiver,
207
+ };
208
+ // Future-proofing: when we add support for optional computed calls,
209
+ // we'll need to copy the value here
210
+ if ((value as any).optional) {
211
+ invariant(
212
+ false,
213
+ "TODO: translate optional computed load to optional property load"
214
+ );
215
+ }
216
+ instr.value = nextValue;
217
+ }
218
+ return null;
219
}
220
case "BinaryExpression": {
147
- const lhsValue = read(constants, instr.left);
148
- const rhsValue = read(constants, instr.right);
221
+ const lhsValue = read(constants, value.left);
222
+ const rhsValue = read(constants, value.right);
223
if (lhsValue !== null && rhsValue !== null) {
224
const lhs = lhsValue.value;
225
const rhs = rhsValue.value;
152
- switch (instr.operator) {
226
+ switch (value.operator) {
227
case "+": {
228
if (typeof lhs === "number" && typeof rhs === "number") {
155
- return { kind: "Primitive", value: lhs + rhs, loc: instr.loc };
229
+ return { kind: "Primitive", value: lhs + rhs, loc: value.loc };
230
}
231
return null;
232
}
233
case "-": {
234
if (typeof lhs === "number" && typeof rhs === "number") {
161
- return { kind: "Primitive", value: lhs - rhs, loc: instr.loc };
235
+ return { kind: "Primitive", value: lhs - rhs, loc: value.loc };
236
}
237
return null;
238
}
239
case "*": {
240
if (typeof lhs === "number" && typeof rhs === "number") {
167
- return { kind: "Primitive", value: lhs * rhs, loc: instr.loc };
241
+ return { kind: "Primitive", value: lhs * rhs, loc: value.loc };
242
}
243
return null;
244
}
245
case "/": {
246
if (typeof lhs === "number" && typeof rhs === "number") {
173
- return { kind: "Primitive", value: lhs / rhs, loc: instr.loc };
247
+ return { kind: "Primitive", value: lhs / rhs, loc: value.loc };
248
}
249
return null;
250
}
251
case "<": {
252
if (typeof lhs === "number" && typeof rhs === "number") {
179
- return { kind: "Primitive", value: lhs < rhs, loc: instr.loc };
253
+ return { kind: "Primitive", value: lhs < rhs, loc: value.loc };
254
}
255
return null;
256
}
257
case "<=": {
258
if (typeof lhs === "number" && typeof rhs === "number") {
185
- return { kind: "Primitive", value: lhs <= rhs, loc: instr.loc };
259
+ return { kind: "Primitive", value: lhs <= rhs, loc: value.loc };
260
}
261
return null;
262
}
263
case ">": {
264
if (typeof lhs === "number" && typeof rhs === "number") {
191
- return { kind: "Primitive", value: lhs > rhs, loc: instr.loc };
265
+ return { kind: "Primitive", value: lhs > rhs, loc: value.loc };
266
}
267
return null;
268
}
269
case ">=": {
270
if (typeof lhs === "number" && typeof rhs === "number") {
197
- return { kind: "Primitive", value: lhs >= rhs, loc: instr.loc };
271
+ return { kind: "Primitive", value: lhs >= rhs, loc: value.loc };
272
}
273
return null;
274
}
275
case "==": {
202
- return { kind: "Primitive", value: lhs == rhs, loc: instr.loc };
276
+ return { kind: "Primitive", value: lhs == rhs, loc: value.loc };
277
}
278
case "===": {
205
- return { kind: "Primitive", value: lhs === rhs, loc: instr.loc };
279
+ return { kind: "Primitive", value: lhs === rhs, loc: value.loc };
280
}
281
case "!=": {
208
- return { kind: "Primitive", value: lhs != rhs, loc: instr.loc };
282
+ return { kind: "Primitive", value: lhs != rhs, loc: value.loc };
283
}
284
case "!==": {
211
- return { kind: "Primitive", value: lhs !== rhs, loc: instr.loc };
285
+ return { kind: "Primitive", value: lhs !== rhs, loc: value.loc };
286
}
287
default: {
288
// TODO: handle more cases
@@ -219,23 +293,23 @@ function evaluateInstruction(
293
return null;
294
}
295
case "PropertyLoad": {
222
- const objectValue = read(constants, instr.object);
296
+ const objectValue = read(constants, value.object);
297
if (objectValue !== null) {
298
if (
299
typeof objectValue.value === "string" &&
226
- instr.property === "length"
300
+ value.property === "length"
301
) {
302
return {
303
kind: "Primitive",
304
value: objectValue.value.length,
231
- loc: instr.loc,
305
+ loc: value.loc,
306
};
307
}
308
}
309
return null;
310
}
311
case "Identifier": {
238
- return read(constants, instr);
312
+ return read(constants, value);
313
}
314
default: {
315
// TODO: handle more cases
compiler/forget/src/__tests__/fixtures/hir/alias-computed-load.expect.md
+1
-1
@@ -23,7 +23,7 @@ function component(a) {
23
if (c_0) {
24
x = { a: a };
25
const y = {};
26
- y.x = x["a"];
26
+ y.x = x.a;
27
28
mutate(y);
29
$[0] = a;
compiler/forget/src/__tests__/fixtures/hir/computed-store-alias.expect.md
+1
-1
@@ -23,7 +23,7 @@ function component(a, b) {
23
if (c_0 || c_1) {
24
const y = { a: a };
25
x = { b: b };
26
- x["y"] = y;
26
+ x.y = y;
27
28
mutate(x);
29
$[0] = a;
compiler/forget/src/__tests__/fixtures/hir/constant-computed.expect.md
new
+35
@@ -0,0 +1,35 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ const index = "foo";
7
+ const x = {};
8
+ x[index] = x[index] + x["bar"];
9
+ x[index](props.foo);
10
+ return x;
11
+}
12
+
13
+```
14
+
15
+## Code
16
+
17
+```javascript
18
+function Component(props) {
19
+ const $ = React.unstable_useMemoCache(2);
20
+ const c_0 = $[0] !== props.foo;
21
+ let x;
22
+ if (c_0) {
23
+ x = {};
24
+ x.foo = x.foo + x.bar;
25
+ x.foo(props.foo);
26
+ $[0] = props.foo;
27
+ $[1] = x;
28
+ } else {
29
+ x = $[1];
30
+ }
31
+ return x;
32
+}
33
+
34
+```
35
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/constant-computed.js
new
+7
@@ -0,0 +1,7 @@
1
+function Component(props) {
2
+ const index = "foo";
3
+ const x = {};
4
+ x[index] = x[index] + x["bar"];
5
+ x[index](props.foo);
6
+ return x;
7
+}