@samitouri / QOS-React-2 / commits / fcdcd6038f

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