@samitouri / QOS-React / commits / 5bd3a39d86

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