@samitouri / QOS-React / commits / b009626431

Improve DCE to handle unused loop phis

Improves DCE, using fixpoint iteration to detect values that are updated across loops but otherwise never read. There is still some further optimization we can do (the dce-loop case could optimize out `y`), but this seems like plenty for now. Actually, we probably have to add some return statements to our fixtures before landing this, because otherwise most of the code goes away.

Joe Savona committed Feb 3, 2023 at 15:46 UTC b0096264315bd4efe9f404c8b65039120c5e8a49
32 files changed +157 -346
compiler/forget/src/Optimization/DeadCodeElimination.ts
+41 -64
@@ -5,18 +5,12 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {
9 - BlockId,
10 - HIRFunction,
11 - Identifier,
12 - Instruction,
13 - InstructionValue,
14 -} from "../HIR";
8 +import { HIRFunction, Identifier, InstructionValue } from "../HIR";
9 import {
10 eachInstructionValueOperand,
11 eachTerminalOperand,
12 } from "../HIR/visitors";
19 -import { assertExhaustive } from "../Utils/utils";
13 +import { assertExhaustive, retainWhere } from "../Utils/utils";
14
15 /**
16 * Implements dead-code elimination, eliminating instructions whose values are unused.
@@ -26,71 +20,54 @@ import { assertExhaustive } from "../Utils/utils";
20 export function deadCodeElimination(fn: HIRFunction): void {
21 const used = new Set<Identifier>();
22
29 - // Find any phi operands involved in a loop, which might otherwise appear as dead
30 - // code when using a reverse iteration.
31 - //
32 - // A more advanced algorithm could still prune some of these operands
33 - // let's keep it simple for now
34 - const seen = new Set<BlockId>();
35 - for (const [, block] of fn.body.blocks) {
36 - for (const phi of block.phis) {
37 - for (const [pred, operand] of phi.operands) {
38 - if (!seen.has(pred)) {
39 - used.add(operand);
40 - }
41 - }
42 - }
43 - seen.add(block.id);
44 - }
45 -
46 - // Iterate blocks in postorder (successors before predecessors, excepting loops)
47 - // to find usages before declarations
48 - const reversedBlocks = [...fn.body.blocks.values()].reverse();
49 - for (const block of reversedBlocks) {
50 - for (const operand of eachTerminalOperand(block.terminal)) {
51 - used.add(operand.identifier);
52 - }
23 + // TODO: ensure a single iteration of the CFG if there are no back-edges,
24 + // see EliminateRedundantPhi.ts for an example
25 + let lastSize = -1;
26 + while (used.size > lastSize) {
27 + lastSize = used.size;
28
54 - let nextInstructions: Array<Instruction> | null = null;
55 - for (let i = block.instructions.length - 1; i >= 0; i--) {
56 - const instr = block.instructions[i]!;
57 - if (
58 - !used.has(instr.lvalue.place.identifier) &&
59 - pruneableValue(instr.value) &&
60 - // Can't prune the last value of a value block, that's its value!
61 - !(block.kind !== "block" && i === block.instructions.length - 1)
62 - ) {
63 - continue;
64 - }
65 - used.add(instr.lvalue.place.identifier);
66 - nextInstructions ??= [];
67 - nextInstructions.push(instr);
68 - for (const operand of eachInstructionValueOperand(instr.value)) {
29 + // Iterate blocks in postorder (successors before predecessors, excepting loops)
30 + // to find usages before declarations
31 + const reversedBlocks = [...fn.body.blocks.values()].reverse();
32 + for (const block of reversedBlocks) {
33 + for (const operand of eachTerminalOperand(block.terminal)) {
34 used.add(operand.identifier);
35 }
71 - }
72 - if (nextInstructions !== null) {
73 - nextInstructions.reverse();
74 - block.instructions = nextInstructions;
75 - }
76 - for (const phi of block.phis) {
77 - if (used.has(phi.id)) {
78 - for (const [, operand] of phi.operands) {
79 - used.add(operand);
36 +
37 + for (let i = block.instructions.length - 1; i >= 0; i--) {
38 + const instr = block.instructions[i]!;
39 + if (
40 + !used.has(instr.lvalue.place.identifier) &&
41 + pruneableValue(instr.value) &&
42 + // Can't prune the last value of a value block, that's its value!
43 + !(block.kind !== "block" && i === block.instructions.length - 1)
44 + ) {
45 + continue;
46 }
81 - } else {
82 - for (const [, operand] of phi.operands) {
83 - if (used.has(operand)) {
84 - used.add(phi.id);
85 - for (const [, operand] of phi.operands) {
86 - used.add(operand);
87 - }
88 - break;
47 + used.add(instr.lvalue.place.identifier);
48 + for (const operand of eachInstructionValueOperand(instr.value)) {
49 + used.add(operand.identifier);
50 + }
51 + }
52 + for (const phi of block.phis) {
53 + if (used.has(phi.id)) {
54 + for (const [pred, operand] of phi.operands) {
55 + used.add(operand);
56 }
57 }
58 }
59 }
60 }
61 + for (const [, block] of fn.body.blocks) {
62 + for (const phi of block.phis) {
63 + if (!used.has(phi.id)) {
64 + block.phis.delete(phi);
65 + }
66 + }
67 + retainWhere(block.instructions, (instr) =>
68 + used.has(instr.lvalue.place.identifier)
69 + );
70 + }
71 }
72
73 /**
compiler/forget/src/__tests__/fixtures/hir/alias-while.expect.md
+2 -11
@@ -30,12 +30,10 @@ function foo(cond) {
30 const $ = React.useMemoCache();
31 const c_0 = $[0] !== cond;
32 let a;
33 - let b;
34 - let c;
33 if (c_0) {
34 a = {};
37 - b = {};
38 - c = {};
35 + let b = {};
36 + let c = {};
37 while (cond) {
38 const z = a;
39 a = b;
@@ -45,16 +43,9 @@ function foo(cond) {
43 }
44 $[0] = cond;
45 $[1] = a;
48 - $[2] = b;
49 - $[3] = c;
46 } else {
47 a = $[1];
52 - b = $[2];
53 - c = $[3];
48 }
55 - a;
56 - b;
57 - c;
49 return a;
50 }
51
compiler/forget/src/__tests__/fixtures/hir/assignment-variations.expect.md
+1 -8
@@ -14,14 +14,7 @@ function f() {
14 ## Code
15
16 ```javascript
17 -function f() {
18 - const x = 1;
19 - 1;
20 - const x$0 = 2;
21 - 1;
22 - const x$1 = 3;
23 - const x$2 = x$1 >>> 1;
24 -}
17 +function f() {}
18
19 ```
20
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/constant-propagation-phi.expect.md
-14
@@ -22,23 +22,9 @@ function foo(a, b, c) {
22
23 ```javascript
24 function foo(a, b, c) {
25 - const x = undefined;
26 - let x$0 = undefined;
25 if (a) {
28 - 2;
29 - 1;
30 -
31 - const x$1 = 1;
32 - x$0 = x$1;
26 } else {
34 - 0;
35 - 1;
36 -
37 - const x$2 = 1;
38 - x$0 = x$2;
27 }
40 - 1;
41 - true;
28 return b;
29 }
30
compiler/forget/src/__tests__/fixtures/hir/dce-loop.expect.md new
+27
@@ -0,0 +1,27 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo(props) {
6 + let x = 0;
7 + let y = 0;
8 + while (y < props.max) {
9 + x++;
10 + y++;
11 + }
12 +}
13 +
14 +```
15 +
16 +## Code
17 +
18 +```javascript
19 +function foo(props) {
20 + let y = 0;
21 + while (y < props.max) {
22 + y = y + 1;
23 + }
24 +}
25 +
26 +```
27 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/dce-loop.js new
+8
@@ -0,0 +1,8 @@
1 +function foo(props) {
2 + let x = 0;
3 + let y = 0;
4 + while (y < props.max) {
5 + x++;
6 + y++;
7 + }
8 +}
compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md
+1 -11
@@ -25,17 +25,7 @@ function foo(a, b, c) {
25 ## Code
26
27 ```javascript
28 -function foo(a, b, c) {
29 - const d = undefined;
30 - const g = undefined;
31 - const n = undefined;
32 - const o = undefined;
33 - const d$0 = a[0];
34 - const g$1 = a[1][0].e.f;
35 -
36 - const n$2 = b.l.m[0][0];
37 - const o$3 = b.o;
38 -}
28 +function foo(a, b, c) {}
29
30 ```
31
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md
+1 -7
@@ -24,13 +24,7 @@ function foo(a, b, c) {
24 ## Code
25
26 ```javascript
27 -function foo(a, b, c) {
28 - const d = a[0];
29 - const f = a[1][0].e.f;
30 -
31 - const n = b.l.m[0][0];
32 - const o = b.o;
33 -}
27 +function foo(a, b, c) {}
28
29 ```
30
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/infer-phi-primitive.expect.md
+9 -10
@@ -21,25 +21,24 @@ function foo(a, b) {
21 ```javascript
22 function foo(a, b) {
23 const $ = React.useMemoCache();
24 - const x = undefined;
24 const c_0 = $[0] !== a;
26 - let x$0;
25 + let x;
26 if (c_0) {
28 - x$0 = undefined;
27 + x = undefined;
28 if (a) {
30 - const x$1 = 1;
31 - x$0 = x$1;
29 + const x$0 = 1;
30 + x = x$0;
31 } else {
33 - const x$2 = 2;
34 - x$0 = x$2;
32 + const x$1 = 2;
33 + x = x$1;
34 }
35 $[0] = a;
37 - $[1] = x$0;
36 + $[1] = x;
37 } else {
39 - x$0 = $[1];
38 + x = $[1];
39 }
40
42 - const y = x$0;
41 + const y = x;
42 return y;
43 }
44
compiler/forget/src/__tests__/fixtures/hir/obj-literal-cached-in-if-else.expect.md
+15 -16
@@ -20,42 +20,41 @@ function foo(a, b, c, d) {
20 ```javascript
21 function foo(a, b, c, d) {
22 const $ = React.useMemoCache();
23 - const x = {};
23 const c_0 = $[0] !== b;
24 const c_1 = $[1] !== c;
26 - let x$0;
25 + let x;
26 if (c_0 || c_1) {
28 - x$0 = undefined;
27 + x = undefined;
28 if (someVal) {
29 const c_3 = $[3] !== b;
31 - let x$1;
30 + let x$0;
31 if (c_3) {
33 - x$1 = { b: b };
32 + x$0 = { b: b };
33 $[3] = b;
35 - $[4] = x$1;
34 + $[4] = x$0;
35 } else {
37 - x$1 = $[4];
36 + x$0 = $[4];
37 }
39 - x$0 = x$1;
38 + x = x$0;
39 } else {
40 const c_5 = $[5] !== c;
42 - let x$2;
41 + let x$1;
42 if (c_5) {
44 - x$2 = { c: c };
43 + x$1 = { c: c };
44 $[5] = c;
46 - $[6] = x$2;
45 + $[6] = x$1;
46 } else {
48 - x$2 = $[6];
47 + x$1 = $[6];
48 }
50 - x$0 = x$2;
49 + x = x$1;
50 }
51 $[0] = b;
52 $[1] = c;
54 - $[2] = x$0;
53 + $[2] = x;
54 } else {
56 - x$0 = $[2];
55 + x = $[2];
56 }
58 - return x$0;
57 + return x;
58 }
59
60 ```
compiler/forget/src/__tests__/fixtures/hir/obj-literal-mutated-after-if-else.expect.md
+10 -11
@@ -21,27 +21,26 @@ function foo(a, b, c, d) {
21 ```javascript
22 function foo(a, b, c, d) {
23 const $ = React.useMemoCache();
24 - const x = {};
24 const c_0 = $[0] !== b;
25 const c_1 = $[1] !== c;
27 - let x$0;
26 + let x;
27 if (c_0 || c_1) {
29 - x$0 = undefined;
28 + x = undefined;
29 if (someVal) {
31 - const x$1 = { b: b };
32 - x$0 = x$1;
30 + const x$0 = { b: b };
31 + x = x$0;
32 } else {
34 - const x$2 = { c: c };
35 - x$0 = x$2;
33 + const x$1 = { c: c };
34 + x = x$1;
35 }
37 - x$0.f = 1;
36 + x.f = 1;
37 $[0] = b;
38 $[1] = c;
40 - $[2] = x$0;
39 + $[2] = x;
40 } else {
42 - x$0 = $[2];
41 + x = $[2];
42 }
44 - return x$0;
43 + return x;
44 }
45
46 ```
compiler/forget/src/__tests__/fixtures/hir/primitive-alias-mutate.expect.md
+9 -10
@@ -21,25 +21,24 @@ function component(a) {
21 ```javascript
22 function component(a) {
23 const $ = React.useMemoCache();
24 - const x = "foo";
24 const c_0 = $[0] !== a;
26 - let x$0;
25 + let x;
26 if (c_0) {
28 - x$0 = undefined;
27 + x = undefined;
28 if (a) {
30 - const x$1 = "bar";
31 - x$0 = x$1;
29 + const x$0 = "bar";
30 + x = x$0;
31 } else {
33 - const x$2 = "baz";
34 - x$0 = x$2;
32 + const x$1 = "baz";
33 + x = x$1;
34 }
35 $[0] = a;
37 - $[1] = x$0;
36 + $[1] = x;
37 } else {
39 - x$0 = $[1];
38 + x = $[1];
39 }
40
42 - const y = x$0;
41 + const y = x;
42 mutate(y);
43 return y;
44 }
compiler/forget/src/__tests__/fixtures/hir/reverse-postorder.expect.md
+13 -48
@@ -36,59 +36,24 @@ function Component(props) {
36
37 ```javascript
38 function Component(props) {
39 - const $ = React.useMemoCache();
40 - const c_0 = $[0] !== props.cond;
41 - const c_1 = $[1] !== props.test;
42 - const c_2 = $[2] !== props.v0;
43 - const c_3 = $[3] !== props.v1;
44 - const c_4 = $[4] !== props.v2;
45 - const c_5 = $[5] !== props.cond2;
46 - const c_6 = $[6] !== props.b;
47 - const c_7 = $[7] !== props.c;
48 - let x;
49 - if (c_0 || c_1 || c_2 || c_3 || c_4 || c_5 || c_6 || c_7) {
50 - x = undefined;
51 - bb1: if (props.cond) {
52 - switch (props.test) {
53 - case 0: {
54 - const x$0 = props.v0;
55 - x = x$0;
56 - break bb1;
57 - }
58 - case 1: {
59 - const x$1 = props.v1;
60 - x = x$1;
61 - break bb1;
62 - }
63 - case 2: {
64 - }
65 - default: {
66 - const x$2 = props.v2;
67 - x = x$2;
68 - }
39 + bb1: if (props.cond) {
40 + switch (props.test) {
41 + case 0: {
42 + break bb1;
43 }
70 - } else {
71 - if (props.cond2) {
72 - const x$3 = props.b;
73 - x = x$3;
74 - } else {
75 - const x$4 = props.c;
76 - x = x$4;
44 + case 1: {
45 + break bb1;
46 + }
47 + case 2: {
48 + }
49 + default: {
50 }
51 }
79 - $[0] = props.cond;
80 - $[1] = props.test;
81 - $[2] = props.v0;
82 - $[3] = props.v1;
83 - $[4] = props.v2;
84 - $[5] = props.cond2;
85 - $[6] = props.b;
86 - $[7] = props.c;
87 - $[8] = x;
52 } else {
89 - x = $[8];
53 + if (props.cond2) {
54 + } else {
55 + }
56 }
91 - x;
57 }
58
59 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-call-jsx-2.expect.md
-1
@@ -40,7 +40,6 @@ function Component(props) {
40 t0 = $[2];
41 }
42 if (t0) {
43 - const _ = <div a={a}></div>;
43 }
44
45 foo(a, b);
compiler/forget/src/__tests__/fixtures/hir/ssa-complex-multiple-if.expect.md
+1 -12
@@ -20,18 +20,7 @@ function foo() {
20 ## Code
21
22 ```javascript
23 -function foo() {
24 - const x = 1;
25 - const y = 2;
26 - 2;
27 - true;
28 -
29 - const x$0 = 3;
30 - 3;
31 - false;
32 -
33 - const y$1 = x$0;
34 -}
23 +function foo() {}
24
25 ```
26
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-complex-single-if.expect.md
+1 -10
@@ -17,16 +17,7 @@ function foo() {
17 ## Code
18
19 ```javascript
20 -function foo() {
21 - const x = 1;
22 - const y = 2;
23 - 2;
24 - true;
25 -
26 - const x$0 = 3;
27 -
28 - const y$1 = x$0;
29 -}
20 +function foo() {}
21
22 ```
23
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-if-else.expect.md
+1 -6
@@ -18,12 +18,7 @@ function foo() {
18 ## Code
19
20 ```javascript
21 -function foo() {
22 - const x = 1;
23 - const y = 2;
24 -
25 - const z = 3;
26 -}
21 +function foo() {}
22
23 ```
24
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md
+1 -4
@@ -25,10 +25,7 @@ function foo(a, b, c) {
25 const x = 0;
26 while (a) {
27 while (b) {
28 - while (c) {
29 - 1;
30 - 1;
31 - }
28 + while (c) {}
29 }
30 }
31 return x;
compiler/forget/src/__tests__/fixtures/hir/ssa-nested-partial-phi.expect.md
+1 -15
@@ -18,23 +18,9 @@ function foo(a, b, c) {
18
19 ```javascript
20 function foo(a, b, c) {
21 - const $ = React.useMemoCache();
22 - const x = a;
21 if (b) {
24 - const c_0 = $[0] !== c;
25 - let x$0;
26 - if (c_0) {
27 - x$0 = x;
28 - if (c) {
29 - const x$1 = c;
30 - x$0 = x$1;
31 - }
32 - $[0] = c;
33 - $[1] = x$0;
34 - } else {
35 - x$0 = $[1];
22 + if (c) {
23 }
37 - x$0;
24 }
25 }
26
compiler/forget/src/__tests__/fixtures/hir/ssa-sibling-phis.expect.md
+1 -8
@@ -27,14 +27,7 @@ function foo(a, b, c, d) {
27 ## Code
28
29 ```javascript
30 -function foo(a, b, c, d) {
31 - const x = 0;
32 - true;
33 - true;
34 -
35 - const x$0 = a;
36 - x$0;
37 -}
30 +function foo(a, b, c, d) {}
31
32 ```
33
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-simple-phi.expect.md
+1 -9
@@ -19,15 +19,7 @@ function foo() {
19 ## Code
20
21 ```javascript
22 -function foo() {
23 - const y = 2;
24 - 1;
25 - true;
26 -
27 - const y$0 = 1;
28 -
29 - const x = y$0;
30 -}
22 +function foo() {}
23
24 ```
25
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-simple.expect.md
+1 -4
@@ -12,10 +12,7 @@ function foo() {
12 ## Code
13
14 ```javascript
15 -function foo() {
16 - const x = 1;
17 - const y = 2;
18 -}
15 +function foo() {}
16
17 ```
18
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-single-if.expect.md
+1 -6
@@ -16,12 +16,7 @@ function foo() {
16 ## Code
17
18 ```javascript
19 -function foo() {
20 - const x = 1;
21 - const y = 2;
22 -
23 - const z = 3;
24 -}
19 +function foo() {}
20
21 ```
22
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md
+8 -30
@@ -28,39 +28,17 @@ function foo() {
28
29 ```javascript
30 function foo() {
31 - const $ = React.useMemoCache();
31 const x = 1;
33 - let x$0;
34 - if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
35 - x$0 = undefined;
36 - bb1: switch (x) {
37 - case 1: {
38 - 1;
39 -
40 - const x$1 = 2;
41 - x$0 = x$1;
42 - break bb1;
43 - }
44 - case 2: {
45 - 2;
46 -
47 - const x$2 = 3;
48 - x$0 = x$2;
49 - break bb1;
50 - }
51 - default: {
52 - 3;
53 -
54 - const x$3 = 4;
55 - x$0 = x$3;
56 - }
32 + bb1: switch (x) {
33 + case 1: {
34 + break bb1;
35 + }
36 + case 2: {
37 + break bb1;
38 + }
39 + default: {
40 }
58 - $[0] = x$0;
59 - } else {
60 - x$0 = $[0];
41 }
62 -
63 - const y = x$0;
42 }
43
44 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md
+1 -4
@@ -18,10 +18,7 @@ function foo() {
18 ```javascript
19 function foo() {
20 const x = 1;
21 - while (true) {
22 - 1;
23 - 2;
24 - }
21 + while (true) {}
22 return x;
23 }
24
compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.expect.md
-6
@@ -38,27 +38,21 @@ function foo(x) {
38 function foo(x) {
39 bb1: switch (x) {
40 case 0: {
41 - const y = 0;
41 }
42 case 1: {
44 - const y = 1;
43 break bb1;
44 }
45 case 2: {
46 break bb1;
47 }
48 case 3: {
51 - const y = 3;
49 break bb1;
50 }
51 case 4: {
55 - const y = 4;
52 }
53 case 5: {
58 - const y = 5;
54 }
55 default: {
61 - const y = 0;
56 }
57 }
58 }
compiler/forget/src/__tests__/fixtures/hir/type-args-test-binary-operator.expect.md
-1
@@ -15,7 +15,6 @@ function component(a, b) {
15 ```javascript
16 function component(a, b) {
17 if (a > b) {
18 - const m = {};
18 }
19 }
20
compiler/forget/src/__tests__/fixtures/hir/type-binary-operator.expect.md
-1
@@ -32,7 +32,6 @@ function component() {
32 b = $[1];
33 }
34 if (a > b) {
35 - const m = {};
35 }
36 }
37
compiler/forget/src/__tests__/fixtures/hir/type-field-load.expect.md
+1 -11
@@ -12,17 +12,7 @@ function component() {
12 ## Code
13
14 ```javascript
15 -function component() {
16 - const $ = React.useMemoCache();
17 - let x;
18 - if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
19 - x = { t: 1 };
20 - $[0] = x;
21 - } else {
22 - x = $[0];
23 - }
24 - const p = x.t;
25 -}
15 +function component() {}
16
17 ```
18
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/type-test-field-load-binary-op.expect.md
-3
@@ -45,9 +45,6 @@ function component() {
45 const v = x.v;
46 if (u > v) {
47 }
48 -
49 - const y = x.u;
50 - const z = x.v;
48 }
49
50 ```
compiler/forget/src/__tests__/fixtures/hir/type-test-primitive.expect.md
+1 -4
@@ -12,10 +12,7 @@ function component() {
12 ## Code
13
14 ```javascript
15 -function component() {
16 - const x = 1;
17 - const y = 2;
18 -}
15 +function component() {}
16
17 ```
18
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/type-test-return-type-inference.expect.md
-1
@@ -34,7 +34,6 @@ function component() {
34 y = $[1];
35 }
36 if (x > y) {
37 - const z = {};
37 }
38
39 const z_0 = foo();