Implement HIR visitors (#747)
Jan Kassens committed
Nov 8, 2022 at 09:55 UTC
5bd3a39d86dc0fe52548b9d4d98416eb17b5ceeb
5 files changed
+203
-225
compiler/forget/src/HIR/EliminateRedundantPhi.ts
+4
-34
@@ -7,8 +7,8 @@
7
8
import invariant from "invariant";
9
import { assertExhaustive } from "../Common/utils";
10
-import { BlockId, HIRFunction, Identifier, Place } from "./HIR";
11
-import { eachInstructionOperand } from "./HIRBuilder";
10
+import { BlockId, HIRFunction, Identifier, Place, Terminal } from "./HIR";
11
+import { eachInstructionOperand, eachTerminalOperand } from "./visitors";
12
13
/**
14
* Pass to eliminate redundant phi nodes:
@@ -91,38 +91,8 @@ export function eliminateRedundantPhi(fn: HIRFunction) {
91
92
// Rewrite all terminal operands
93
const { terminal } = block;
94
- switch (terminal.kind) {
95
- case "if": {
96
- rewritePlace(terminal.test, rewrites);
97
- break;
98
- }
99
- case "switch": {
100
- rewritePlace(terminal.test, rewrites);
101
- for (const case_ of terminal.cases) {
102
- if (case_.test === null) {
103
- continue;
104
- }
105
- rewritePlace(case_.test, rewrites);
106
- }
107
- break;
108
- }
109
- case "return":
110
- case "throw": {
111
- if (terminal.value !== null) {
112
- rewritePlace(terminal.value, rewrites);
113
- }
114
- break;
115
- }
116
- case "goto": {
117
- // no-op
118
- break;
119
- }
120
- default: {
121
- assertExhaustive(
122
- terminal,
123
- `Unexpected terminal kind '${(terminal as any).kind}'`
124
- );
125
- }
94
+ for (const place of eachTerminalOperand(terminal)) {
95
+ rewritePlace(place, rewrites);
96
}
97
}
98
// We only need to loop if there were newly eliminated phis in this iteration
compiler/forget/src/HIR/HIRBuilder.ts
+1
-124
@@ -19,6 +19,7 @@ import {
19
Terminal,
20
} from "./HIR";
21
import { printInstruction } from "./PrintHIR";
22
+import { mapTerminalSuccessors } from "./visitors";
23
24
// *******************************************************************************************
25
// *******************************************************************************************
@@ -553,127 +554,3 @@ function getTargetIfIndirection(block: BasicBlock): number | null {
554
? block.terminal.block
555
: null;
556
}
556
-
557
-/**
558
- * Maps a terminal node's block assignments using the provided function.
559
- *
560
- * TODO: this visits successors in reverse ordering to facilitate shrink()'s
561
- * goal of producing a reverse postorder graph where siblings are in-order.
562
- */
563
-export function mapTerminalSuccessors(
564
- terminal: Terminal,
565
- fn: (block: BlockId, isFallthrough: boolean) => BlockId
566
-): Terminal {
567
- switch (terminal.kind) {
568
- case "goto": {
569
- const target = fn(terminal.block, false);
570
- return {
571
- kind: "goto",
572
- block: target,
573
- };
574
- }
575
- case "if": {
576
- const consequent = fn(terminal.consequent, false);
577
- const alternate = fn(terminal.alternate, false);
578
- const fallthrough =
579
- terminal.fallthrough !== null ? fn(terminal.fallthrough, true) : null;
580
- return {
581
- kind: "if",
582
- test: terminal.test,
583
- consequent,
584
- alternate,
585
- fallthrough,
586
- };
587
- }
588
- case "switch": {
589
- const cases = terminal.cases.map((case_) => {
590
- const target = fn(case_.block, false);
591
- return {
592
- test: case_.test,
593
- block: target,
594
- };
595
- });
596
- const fallthrough =
597
- terminal.fallthrough !== null ? fn(terminal.fallthrough, true) : null;
598
- return {
599
- kind: "switch",
600
- test: terminal.test,
601
- cases,
602
- fallthrough,
603
- };
604
- }
605
- case "return": {
606
- return {
607
- kind: "return",
608
- value: terminal.value,
609
- };
610
- }
611
- case "throw": {
612
- return terminal;
613
- }
614
- default: {
615
- assertExhaustive(
616
- terminal,
617
- `Unexpected terminal kind '${(terminal as any as Terminal).kind}'`
618
- );
619
- }
620
- }
621
-}
622
-
623
-export function* eachInstructionOperand(instr: Instruction) {
624
- const instrValue = instr.value;
625
- switch (instrValue.kind) {
626
- case "NewExpression":
627
- case "CallExpression": {
628
- yield instrValue.callee;
629
- yield* instrValue.args;
630
- break;
631
- }
632
- case "BinaryExpression": {
633
- yield instrValue.left;
634
- yield instrValue.right;
635
- break;
636
- }
637
- case "Identifier": {
638
- yield instrValue;
639
- break;
640
- }
641
- case "UnaryExpression": {
642
- yield instrValue.value;
643
- break;
644
- }
645
- case "JsxExpression": {
646
- yield instrValue.tag;
647
- yield* instrValue.props.values();
648
- if (instrValue.children) {
649
- yield* instrValue.children;
650
- }
651
- break;
652
- }
653
- case "JsxFragment": {
654
- yield* instrValue.children;
655
- break;
656
- }
657
- case "ObjectExpression": {
658
- if (instrValue.properties !== null) {
659
- yield* instrValue.properties.values();
660
- }
661
- break;
662
- }
663
- case "ArrayExpression": {
664
- yield* instrValue.elements;
665
- break;
666
- }
667
- case "OtherStatement":
668
- case "Primitive":
669
- case "JSXText": {
670
- break;
671
- }
672
- default: {
673
- assertExhaustive(
674
- instrValue,
675
- `Unexpected instruction kind '${(instrValue as any).kind}'`
676
- );
677
- }
678
- }
679
-}
compiler/forget/src/HIR/InferMutableLifetimes.ts
+1
-1
@@ -7,7 +7,7 @@
7
8
import { assertExhaustive } from "../Common/utils";
9
import { Effect, HIRFunction, Instruction, Place } from "./HIR";
10
-import { eachInstructionOperand } from "./HIRBuilder";
10
+import { eachInstructionOperand } from "./visitors";
11
import { printInstruction, printPlace } from "./PrintHIR";
12
13
/**
compiler/forget/src/HIR/InferReferenceEffects.ts
+31
-66
@@ -19,7 +19,11 @@ import {
19
Terminal,
20
ValueKind,
21
} from "./HIR";
22
-import { mapTerminalSuccessors } from "./HIRBuilder";
22
+import {
23
+ eachInstructionOperand,
24
+ eachTerminalOperand,
25
+ mapTerminalSuccessors,
26
+} from "./visitors";
27
import { printMixedHIR, printPlace, printSourceLocation } from "./PrintHIR";
28
29
/**
@@ -503,57 +507,44 @@ function inferBlock(env: Environment, block: BasicBlock) {
507
508
for (const instr of block.instructions) {
509
const instrValue = instr.value;
510
+ let effectKind: Effect | null = null;
511
let valueKind: ValueKind;
512
switch (instrValue.kind) {
513
case "BinaryExpression": {
514
valueKind = ValueKind.Immutable;
510
- env.reference(instrValue.left, Effect.Read);
511
- env.reference(instrValue.right, Effect.Read);
515
+ effectKind = Effect.Read;
516
break;
517
}
518
case "ArrayExpression": {
519
valueKind = ValueKind.Mutable;
516
- for (const element of instrValue.elements) {
517
- env.reference(element, Effect.Read);
518
- }
520
+ effectKind = Effect.Read;
521
break;
522
}
523
case "NewExpression": {
524
valueKind = ValueKind.Mutable;
523
- env.reference(instrValue.callee, Effect.Mutate);
524
- for (const arg of instrValue.args) {
525
- env.reference(arg, Effect.Mutate);
526
- }
525
+ effectKind = Effect.Mutate;
526
break;
527
}
528
case "CallExpression": {
530
- let effectKind = Effect.Mutate;
529
valueKind = ValueKind.Mutable;
530
+ effectKind = Effect.Mutate;
531
const hook = parseHookCall(instrValue.callee);
532
if (hook !== null) {
533
effectKind = hook.effectKind;
534
valueKind = hook.valueKind;
535
}
537
- env.reference(instrValue.callee, effectKind);
538
- for (const arg of instrValue.args) {
539
- env.reference(arg, effectKind);
540
- }
536
break;
537
}
538
case "ObjectExpression": {
539
valueKind = ValueKind.Mutable;
540
// Object construction captures but does not modify the key/property values
546
- if (instrValue.properties !== null) {
547
- for (const [_key, value] of instrValue.properties) {
548
- env.reference(value, Effect.Read);
549
- }
550
- }
541
+ effectKind = Effect.Read;
542
break;
543
}
544
case "UnaryExpression": {
545
// TODO check that value must be a primitive, or make conditional based on the operator
546
valueKind = ValueKind.Immutable;
556
- env.reference(instrValue.value, Effect.Read);
547
+ effectKind = Effect.Read;
548
break;
549
}
550
case "OtherStatement": {
@@ -563,22 +554,12 @@ function inferBlock(env: Environment, block: BasicBlock) {
554
}
555
case "JsxExpression": {
556
valueKind = ValueKind.Frozen;
566
- env.reference(instrValue.tag, Effect.Freeze);
567
- for (const [_prop, value] of instrValue.props) {
568
- env.reference(value, Effect.Freeze);
569
- }
570
- if (instrValue.children !== null) {
571
- for (const child of instrValue.children) {
572
- env.reference(child, Effect.Freeze);
573
- }
574
- }
557
+ effectKind = Effect.Freeze;
558
break;
559
}
560
case "JsxFragment": {
561
valueKind = ValueKind.Frozen;
579
- for (const child of instrValue.children) {
580
- env.reference(child, Effect.Freeze);
581
- }
562
+ effectKind = Effect.Freeze;
563
break;
564
}
565
case "JSXText":
@@ -615,6 +596,16 @@ function inferBlock(env: Environment, block: BasicBlock) {
596
assertExhaustive(instrValue, "Unexpected instruction kind");
597
}
598
}
599
+
600
+ for (const operand of eachInstructionOperand(instr)) {
601
+ invariant(
602
+ effectKind != null,
603
+ "effectKind must be set for instruction value `%s`",
604
+ instrValue.kind
605
+ );
606
+ env.reference(operand, effectKind);
607
+ }
608
+
609
env.initialize(instrValue, valueKind);
610
if (instr.lvalue !== null) {
611
if (instr.lvalue.place.memberPath === null) {
@@ -625,39 +616,13 @@ function inferBlock(env: Environment, block: BasicBlock) {
616
instr.lvalue.place.effect = Effect.Mutate;
617
}
618
}
628
- switch (block.terminal.kind) {
629
- case "throw": {
630
- env.reference(block.terminal.value, Effect.Freeze);
631
- break;
632
- }
633
- case "return": {
634
- if (block.terminal.value !== null) {
635
- env.reference(block.terminal.value, Effect.Freeze);
636
- }
637
- break;
638
- }
639
- case "if": {
640
- env.reference(block.terminal.test, Effect.Read);
641
- break;
642
- }
643
- case "switch": {
644
- env.reference(block.terminal.test, Effect.Read);
645
- for (const case_ of block.terminal.cases) {
646
- if (case_.test !== null) {
647
- env.reference(case_.test, Effect.Read);
648
- }
649
- }
650
- break;
651
- }
652
- case "goto": {
653
- break;
654
- }
655
- default: {
656
- assertExhaustive(
657
- block.terminal,
658
- `Unexpected terminal kind '${(block.terminal as any as Terminal).kind}'`
659
- );
660
- }
619
+
620
+ const effect =
621
+ block.terminal.kind === "return" || block.terminal.kind === "throw"
622
+ ? Effect.Freeze
623
+ : Effect.Read;
624
+ for (const operand of eachTerminalOperand(block.terminal)) {
625
+ env.reference(operand, effect);
626
}
627
}
628
compiler/forget/src/HIR/visitors.ts
new
+166
@@ -0,0 +1,166 @@
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 { assertExhaustive } from "../Common/utils";
9
+import { BlockId, Instruction, Place, Terminal } from "./HIR";
10
+
11
+export function* eachInstructionOperand(instr: Instruction): Iterable<Place> {
12
+ const instrValue = instr.value;
13
+ switch (instrValue.kind) {
14
+ case "NewExpression":
15
+ case "CallExpression": {
16
+ yield instrValue.callee;
17
+ yield* instrValue.args;
18
+ break;
19
+ }
20
+ case "BinaryExpression": {
21
+ yield instrValue.left;
22
+ yield instrValue.right;
23
+ break;
24
+ }
25
+ case "Identifier": {
26
+ yield instrValue;
27
+ break;
28
+ }
29
+ case "UnaryExpression": {
30
+ yield instrValue.value;
31
+ break;
32
+ }
33
+ case "JsxExpression": {
34
+ yield instrValue.tag;
35
+ yield* instrValue.props.values();
36
+ if (instrValue.children) {
37
+ yield* instrValue.children;
38
+ }
39
+ break;
40
+ }
41
+ case "JsxFragment": {
42
+ yield* instrValue.children;
43
+ break;
44
+ }
45
+ case "ObjectExpression": {
46
+ if (instrValue.properties !== null) {
47
+ yield* instrValue.properties.values();
48
+ }
49
+ break;
50
+ }
51
+ case "ArrayExpression": {
52
+ yield* instrValue.elements;
53
+ break;
54
+ }
55
+ case "OtherStatement":
56
+ case "Primitive":
57
+ case "JSXText": {
58
+ break;
59
+ }
60
+ default: {
61
+ assertExhaustive(
62
+ instrValue,
63
+ `Unexpected instruction kind '${(instrValue as any).kind}'`
64
+ );
65
+ }
66
+ }
67
+}
68
+
69
+/**
70
+ * Maps a terminal node's block assignments using the provided function.
71
+ */
72
+export function mapTerminalSuccessors(
73
+ terminal: Terminal,
74
+ fn: (block: BlockId, isFallthrough: boolean) => BlockId
75
+): Terminal {
76
+ switch (terminal.kind) {
77
+ case "goto": {
78
+ const target = fn(terminal.block, false);
79
+ return {
80
+ kind: "goto",
81
+ block: target,
82
+ };
83
+ }
84
+ case "if": {
85
+ const consequent = fn(terminal.consequent, false);
86
+ const alternate = fn(terminal.alternate, false);
87
+ const fallthrough =
88
+ terminal.fallthrough !== null ? fn(terminal.fallthrough, true) : null;
89
+ return {
90
+ kind: "if",
91
+ test: terminal.test,
92
+ consequent,
93
+ alternate,
94
+ fallthrough,
95
+ };
96
+ }
97
+ case "switch": {
98
+ const cases = terminal.cases.map((case_) => {
99
+ const target = fn(case_.block, false);
100
+ return {
101
+ test: case_.test,
102
+ block: target,
103
+ };
104
+ });
105
+ const fallthrough =
106
+ terminal.fallthrough !== null ? fn(terminal.fallthrough, true) : null;
107
+ return {
108
+ kind: "switch",
109
+ test: terminal.test,
110
+ cases,
111
+ fallthrough,
112
+ };
113
+ }
114
+ case "return": {
115
+ return {
116
+ kind: "return",
117
+ value: terminal.value,
118
+ };
119
+ }
120
+ case "throw": {
121
+ return terminal;
122
+ }
123
+ default: {
124
+ assertExhaustive(
125
+ terminal,
126
+ `Unexpected terminal kind '${(terminal as any as Terminal).kind}'`
127
+ );
128
+ }
129
+ }
130
+}
131
+
132
+export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
133
+ switch (terminal.kind) {
134
+ case "if": {
135
+ yield terminal.test;
136
+ break;
137
+ }
138
+ case "switch": {
139
+ yield terminal.test;
140
+ for (const case_ of terminal.cases) {
141
+ if (case_.test === null) {
142
+ continue;
143
+ }
144
+ yield case_.test;
145
+ }
146
+ break;
147
+ }
148
+ case "return":
149
+ case "throw": {
150
+ if (terminal.value !== null) {
151
+ yield terminal.value;
152
+ }
153
+ break;
154
+ }
155
+ case "goto": {
156
+ // no-op
157
+ break;
158
+ }
159
+ default: {
160
+ assertExhaustive(
161
+ terminal,
162
+ `Unexpected terminal kind '${(terminal as any).kind}'`
163
+ );
164
+ }
165
+ }
166
+}