From fcdcd6038fd72c3a6a5c183f367446fd711ba2b0 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 17 Feb 2023 12:32:30 -0800 Subject: [PATCH] Constant propagation converts computed access to static property where possible --- .../src/Optimization/ConstantPropagation.ts | 120 ++++++++++++++---- .../hir/alias-computed-load.expect.md | 2 +- .../hir/computed-store-alias.expect.md | 2 +- .../fixtures/hir/constant-computed.expect.md | 35 +++++ .../fixtures/hir/constant-computed.js | 7 + 5 files changed, 141 insertions(+), 25 deletions(-) create mode 100644 compiler/forget/src/__tests__/fixtures/hir/constant-computed.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/constant-computed.js diff --git a/compiler/forget/src/Optimization/ConstantPropagation.ts b/compiler/forget/src/Optimization/ConstantPropagation.ts index 74ab9b5bba..4df3188efb 100644 --- a/compiler/forget/src/Optimization/ConstantPropagation.ts +++ b/compiler/forget/src/Optimization/ConstantPropagation.ts @@ -5,10 +5,13 @@ * LICENSE file in the root directory of this source tree. */ +import { isValidIdentifier } from "@babel/types"; +import invariant from "invariant"; import { GotoVariant, HIRFunction, IdentifierId, + Instruction, InstructionValue, markInstructionIds, markPredecessors, @@ -97,7 +100,7 @@ function applyConstantPropagation(fn: HIRFunction): boolean { } for (const instr of block.instructions) { - const value = evaluateInstruction(constants, instr.value); + const value = evaluateInstruction(constants, instr); if (value !== null) { instr.value = value; constants.set(instr.lvalue.place.identifier.id, value); @@ -137,78 +140,149 @@ function applyConstantPropagation(fn: HIRFunction): boolean { function evaluateInstruction( constants: Constants, - instr: InstructionValue + instr: Instruction ): Constant | null { - switch (instr.kind) { + const value = instr.value; + switch (value.kind) { case "Primitive": { - return instr; + return value; + } + case "ComputedLoad": { + const property = read(constants, value.property); + if ( + property !== null && + typeof property.value === "string" && + isValidIdentifier(property.value) + ) { + const nextValue: InstructionValue = { + kind: "PropertyLoad", + loc: value.loc, + property: property.value, + object: value.object, + optional: false, + }; + // Future-proofing: when we add support for optional computed properties, + // we'll need to copy the value here + if ((value as any).optional) { + invariant( + false, + "TODO: translate optional computed load to optional property load" + ); + } + instr.value = nextValue; + } + return null; + } + case "ComputedStore": { + const property = read(constants, value.property); + if ( + property !== null && + typeof property.value === "string" && + isValidIdentifier(property.value) + ) { + const nextValue: InstructionValue = { + kind: "PropertyStore", + loc: value.loc, + property: property.value, + object: value.object, + value: value.value, + }; + instr.value = nextValue; + } + return null; + } + case "ComputedCall": { + const property = read(constants, value.property); + if ( + property !== null && + typeof property.value === "string" && + isValidIdentifier(property.value) + ) { + const nextValue: InstructionValue = { + kind: "PropertyCall", + args: value.args, + loc: value.loc, + property: property.value, + receiver: value.receiver, + }; + // Future-proofing: when we add support for optional computed calls, + // we'll need to copy the value here + if ((value as any).optional) { + invariant( + false, + "TODO: translate optional computed load to optional property load" + ); + } + instr.value = nextValue; + } + return null; } case "BinaryExpression": { - const lhsValue = read(constants, instr.left); - const rhsValue = read(constants, instr.right); + const lhsValue = read(constants, value.left); + const rhsValue = read(constants, value.right); if (lhsValue !== null && rhsValue !== null) { const lhs = lhsValue.value; const rhs = rhsValue.value; - switch (instr.operator) { + switch (value.operator) { case "+": { if (typeof lhs === "number" && typeof rhs === "number") { - return { kind: "Primitive", value: lhs + rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs + rhs, loc: value.loc }; } return null; } case "-": { if (typeof lhs === "number" && typeof rhs === "number") { - return { kind: "Primitive", value: lhs - rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs - rhs, loc: value.loc }; } return null; } case "*": { if (typeof lhs === "number" && typeof rhs === "number") { - return { kind: "Primitive", value: lhs * rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs * rhs, loc: value.loc }; } return null; } case "/": { if (typeof lhs === "number" && typeof rhs === "number") { - return { kind: "Primitive", value: lhs / rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs / rhs, loc: value.loc }; } return null; } case "<": { if (typeof lhs === "number" && typeof rhs === "number") { - return { kind: "Primitive", value: lhs < rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs < rhs, loc: value.loc }; } return null; } case "<=": { if (typeof lhs === "number" && typeof rhs === "number") { - return { kind: "Primitive", value: lhs <= rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs <= rhs, loc: value.loc }; } return null; } case ">": { if (typeof lhs === "number" && typeof rhs === "number") { - return { kind: "Primitive", value: lhs > rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs > rhs, loc: value.loc }; } return null; } case ">=": { if (typeof lhs === "number" && typeof rhs === "number") { - return { kind: "Primitive", value: lhs >= rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs >= rhs, loc: value.loc }; } return null; } case "==": { - return { kind: "Primitive", value: lhs == rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs == rhs, loc: value.loc }; } case "===": { - return { kind: "Primitive", value: lhs === rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs === rhs, loc: value.loc }; } case "!=": { - return { kind: "Primitive", value: lhs != rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs != rhs, loc: value.loc }; } case "!==": { - return { kind: "Primitive", value: lhs !== rhs, loc: instr.loc }; + return { kind: "Primitive", value: lhs !== rhs, loc: value.loc }; } default: { // TODO: handle more cases @@ -219,23 +293,23 @@ function evaluateInstruction( return null; } case "PropertyLoad": { - const objectValue = read(constants, instr.object); + const objectValue = read(constants, value.object); if (objectValue !== null) { if ( typeof objectValue.value === "string" && - instr.property === "length" + value.property === "length" ) { return { kind: "Primitive", value: objectValue.value.length, - loc: instr.loc, + loc: value.loc, }; } } return null; } case "Identifier": { - return read(constants, instr); + return read(constants, value); } default: { // TODO: handle more cases diff --git a/compiler/forget/src/__tests__/fixtures/hir/alias-computed-load.expect.md b/compiler/forget/src/__tests__/fixtures/hir/alias-computed-load.expect.md index 355cac4199..37c3dd3872 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/alias-computed-load.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/alias-computed-load.expect.md @@ -23,7 +23,7 @@ function component(a) { if (c_0) { x = { a: a }; const y = {}; - y.x = x["a"]; + y.x = x.a; mutate(y); $[0] = a; diff --git a/compiler/forget/src/__tests__/fixtures/hir/computed-store-alias.expect.md b/compiler/forget/src/__tests__/fixtures/hir/computed-store-alias.expect.md index 8c2e8c131d..aaeb0628ce 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/computed-store-alias.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/computed-store-alias.expect.md @@ -23,7 +23,7 @@ function component(a, b) { if (c_0 || c_1) { const y = { a: a }; x = { b: b }; - x["y"] = y; + x.y = y; mutate(x); $[0] = a; diff --git a/compiler/forget/src/__tests__/fixtures/hir/constant-computed.expect.md b/compiler/forget/src/__tests__/fixtures/hir/constant-computed.expect.md new file mode 100644 index 0000000000..3ab3c2f61e --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/constant-computed.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +function Component(props) { + const index = "foo"; + const x = {}; + x[index] = x[index] + x["bar"]; + x[index](props.foo); + return x; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props.foo; + let x; + if (c_0) { + x = {}; + x.foo = x.foo + x.bar; + x.foo(props.foo); + $[0] = props.foo; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/constant-computed.js b/compiler/forget/src/__tests__/fixtures/hir/constant-computed.js new file mode 100644 index 0000000000..2ffe50fef5 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/constant-computed.js @@ -0,0 +1,7 @@ +function Component(props) { + const index = "foo"; + const x = {}; + x[index] = x[index] + x["bar"]; + x[index](props.foo); + return x; +}