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

Separate eachLValue/eachOperand visitors

This PR starts to clean up our handling of lvalues and rvalues by adding new `eachInstructionLValues()` and `mapInstructionLValues()` helpers. Now, `eachInstructionOperand()` and `mapInstructionOperands()` only visit true rvalues, and the new passes must be used to visit lvalues. This allows us to remove the special-casing for StoreLocal and Destructure in most of the passes.

Joe Savona committed Mar 6, 2023 at 14:33 UTC b793fe424d66735385900d01d7bd85a01cfce5f2
11 files changed +104 -104
compiler/forget/src/HIR/ValidateConsistentIdentifiers.ts
+8 -2
@@ -14,7 +14,11 @@ import {
14 SourceLocation,
15 } from "./HIR";
16 import { printPlace } from "./PrintHIR";
17 -import { eachInstructionValueOperand, eachTerminalOperand } from "./visitors";
17 +import {
18 + eachInstructionLValue,
19 + eachInstructionValueOperand,
20 + eachTerminalOperand,
21 +} from "./visitors";
22
23 /**
24 * Validation pass to check that there is a 1:1 mapping between Identifier objects and IdentifierIds,
@@ -46,7 +50,9 @@ export function validateConsistentIdentifiers(fn: HIRFunction): void {
50 );
51 }
52 assignments.add(instr.lvalue.identifier.id);
49 - validate(identifiers, instr.lvalue.identifier, instr.lvalue.loc);
53 + for (const operand of eachInstructionLValue(instr)) {
54 + validate(identifiers, operand.identifier, operand.loc);
55 + }
56 for (const operand of eachInstructionValueOperand(instr.value)) {
57 validate(identifiers, operand.identifier, operand.loc);
58 }
compiler/forget/src/HIR/visitors.ts
+39 -35
@@ -7,19 +7,34 @@
7
8 import { assertExhaustive } from "../Utils/utils";
9 import {
10 - BasicBlock,
10 BlockId,
12 - HIR,
11 Instruction,
12 InstructionValue,
13 makeInstructionId,
14 Pattern,
15 Place,
18 - ReactiveScope,
19 - ScopeId,
16 + ReactiveInstruction,
17 Terminal,
18 } from "./HIR";
19
20 +export function* eachInstructionLValue(
21 + instr: ReactiveInstruction
22 +): Iterable<Place> {
23 + if (instr.lvalue !== null) {
24 + yield instr.lvalue;
25 + }
26 + switch (instr.value.kind) {
27 + case "StoreLocal": {
28 + yield instr.value.lvalue.place;
29 + break;
30 + }
31 + case "Destructure": {
32 + yield* eachPatternOperand(instr.value.lvalue.pattern);
33 + break;
34 + }
35 + }
36 +}
37 +
38 export function* eachInstructionOperand(instr: Instruction): Iterable<Place> {
39 yield* eachInstructionValueOperand(instr.value);
40 }
@@ -54,12 +69,10 @@ export function* eachInstructionValueOperand(
69 break;
70 }
71 case "StoreLocal": {
57 - yield instrValue.lvalue.place;
72 yield instrValue.value;
73 break;
74 }
75 case "Destructure": {
62 - yield* eachPatternOperand(instrValue.lvalue.pattern);
76 yield instrValue.value;
77 break;
78 }
@@ -200,6 +213,26 @@ export function* eachPatternOperand(pattern: Pattern): Iterable<Place> {
213 }
214 }
215
216 +export function mapInstructionLValues(
217 + instr: Instruction,
218 + fn: (place: Place) => Place
219 +): void {
220 + switch (instr.value.kind) {
221 + case "StoreLocal": {
222 + const lvalue = instr.value.lvalue;
223 + lvalue.place = fn(lvalue.place);
224 + break;
225 + }
226 + case "Destructure": {
227 + mapPatternOperands(instr.value.lvalue.pattern, fn);
228 + break;
229 + }
230 + }
231 + if (instr.lvalue !== null) {
232 + instr.lvalue = fn(instr.lvalue);
233 + }
234 +}
235 +
236 export function mapInstructionOperands(
237 instr: Instruction,
238 fn: (place: Place) => Place
@@ -671,32 +704,3 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
704 }
705 }
706 }
674 -
675 -/**
676 - * Iterates over all {@link Place}s within a {@link BasicBlock}.
677 - */
678 -export function* eachBlockOperand(block: BasicBlock): Iterable<Place> {
679 - for (const instr of block.instructions) {
680 - yield* eachInstructionOperand(instr);
681 - if (instr.lvalue != null) {
682 - yield instr.lvalue;
683 - }
684 - }
685 - yield* eachTerminalOperand(block.terminal);
686 -}
687 -
688 -export function* eachReactiveScope(ir: HIR): Iterable<ReactiveScope> {
689 - const seenScopes: Set<ScopeId> = new Set();
690 - for (const [, block] of ir.blocks) {
691 - for (const operand of eachBlockOperand(block)) {
692 - const scope = operand.identifier.scope;
693 - if (scope != null) {
694 - if (seenScopes.has(scope.id)) {
695 - continue;
696 - }
697 - seenScopes.add(scope.id);
698 - yield scope;
699 - }
700 - }
701 - }
702 -}
compiler/forget/src/Inference/InferAliasForStores.ts
+3 -7
@@ -12,8 +12,8 @@ import {
12 Place,
13 } from "../HIR/HIR";
14 import {
15 + eachInstructionLValue,
16 eachInstructionValueOperand,
16 - eachPatternOperand,
17 } from "../HIR/visitors";
18 import DisjointSet from "../Utils/DisjointSet";
19
@@ -27,12 +27,8 @@ export function inferAliasForStores(
27 if (lvalue.effect !== Effect.Store) {
28 continue;
29 }
30 - if (value.kind === "StoreLocal") {
31 - maybeAlias(aliases, value.lvalue.place, value.value, instr.id);
32 - } else if (value.kind === "Destructure") {
33 - for (const place of eachPatternOperand(value.lvalue.pattern)) {
34 - maybeAlias(aliases, place, value.value, instr.id);
35 - }
30 + for (const operand of eachInstructionLValue(instr)) {
31 + maybeAlias(aliases, lvalue, operand, instr.id);
32 }
33 for (const operand of eachInstructionValueOperand(value)) {
34 if (
compiler/forget/src/Inference/InferMutableLifetimes.ts
+13 -25
@@ -14,7 +14,7 @@ import {
14 Place,
15 } from "../HIR/HIR";
16 import { printInstruction, printPlace } from "../HIR/PrintHIR";
17 -import { eachInstructionOperand, eachPatternOperand } from "../HIR/visitors";
17 +import { eachInstructionLValue, eachInstructionOperand } from "../HIR/visitors";
18 import { assertExhaustive } from "../Utils/utils";
19
20 /**
@@ -118,32 +118,20 @@ export function inferMutableLifetimes(
118 }
119
120 for (const instr of block.instructions) {
121 - if (instr.value.kind === "StoreLocal") {
122 - inferPlace(instr.value.value, instr, inferMutableRangeForStores);
123 - instr.value.lvalue.place.identifier.mutableRange.start = instr.id;
124 - instr.value.lvalue.place.identifier.mutableRange.end =
125 - makeInstructionId(instr.id + 1);
126 - } else if (instr.value.kind === "Destructure") {
127 - inferPlace(instr.value.value, instr, inferMutableRangeForStores);
128 - for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
129 - place.identifier.mutableRange.start = instr.id;
130 - place.identifier.mutableRange.end = makeInstructionId(instr.id + 1);
131 - }
132 - } else {
133 - for (const input of eachInstructionOperand(instr)) {
134 - inferPlace(input, instr, inferMutableRangeForStores);
135 - }
136 - }
137 -
138 - const lvalueId = instr.lvalue.identifier;
121 + for (const operand of eachInstructionLValue(instr)) {
122 + const lvalueId = operand.identifier;
123
140 - // lvalue start being mutable when they're initially assigned a
141 - // value.
142 - lvalueId.mutableRange.start = instr.id;
124 + // lvalue start being mutable when they're initially assigned a
125 + // value.
126 + lvalueId.mutableRange.start = instr.id;
127
144 - // Let's be optimistic and assume this lvalue is not mutable by
145 - // default.
146 - lvalueId.mutableRange.end = makeInstructionId(instr.id + 1);
128 + // Let's be optimistic and assume this lvalue is not mutable by
129 + // default.
130 + lvalueId.mutableRange.end = makeInstructionId(instr.id + 1);
131 + }
132 + for (const operand of eachInstructionOperand(instr)) {
133 + inferPlace(operand, instr, inferMutableRangeForStores);
134 + }
135 }
136 }
137 }
compiler/forget/src/ReactiveScopes/BuildReactiveBlocks.ts
+12 -11
@@ -18,6 +18,7 @@ import {
18 ReactiveStatement,
19 ScopeId,
20 } from "../HIR";
21 +import { eachInstructionLValue } from "../HIR/visitors";
22 import { assertExhaustive } from "../Utils/utils";
23 import { eachReactiveValueOperand, mapTerminalBlocks } from "./visitors";
24
@@ -178,22 +179,22 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
179 }
180 }
181
181 -export function getInstructionScope({
182 - id,
183 - lvalue,
184 - value,
185 -}: ReactiveInstruction): ReactiveScope | null {
182 +export function getInstructionScope(
183 + instr: ReactiveInstruction
184 +): ReactiveScope | null {
185 invariant(
187 - lvalue !== null,
186 + instr.lvalue !== null,
187 "Expected lvalues to not be null when assigning scopes. " +
188 "Pruning lvalues too early can result in missing scope information."
189 );
191 - const lvalueScope = getPlaceScope(id, lvalue);
192 - if (lvalueScope !== null) {
193 - return lvalueScope;
190 + for (const operand of eachInstructionLValue(instr)) {
191 + const operandScope = getPlaceScope(instr.id, operand);
192 + if (operandScope !== null) {
193 + return operandScope;
194 + }
195 }
195 - for (const operand of eachReactiveValueOperand(value)) {
196 - const operandScope = getPlaceScope(id, operand);
196 + for (const operand of eachReactiveValueOperand(instr.value)) {
197 + const operandScope = getPlaceScope(instr.id, operand);
198 if (operandScope !== null) {
199 return operandScope;
200 }
compiler/forget/src/ReactiveScopes/InferReactiveIdentifiers.ts
+4
@@ -13,6 +13,7 @@ import {
13 ReactiveFunction,
14 ReactiveInstruction,
15 } from "../HIR/HIR";
16 +import { eachInstructionLValue } from "../HIR/visitors";
17 import { assertExhaustive } from "../Utils/utils";
18 import {
19 eachReactiveValueOperand,
@@ -66,6 +67,9 @@ class Visitor extends ReactiveFunctionVisitor<State> {
67 state.reactivityMap.set(lval.identifier.id, hasReactiveInput);
68
69 if (hasReactiveInput) {
70 + for (const lvalue of eachInstructionLValue(instr)) {
71 + state.reactivityMap.set(lvalue.identifier.id, true);
72 + }
73 // all mutating effects must also be marked as reactive
74 for (const operand of eachReactiveValueOperand(value)) {
75 switch (operand.effect) {
compiler/forget/src/ReactiveScopes/visitors.ts
+6 -3
@@ -17,7 +17,10 @@ import {
17 ReactiveTerminalStatement,
18 ReactiveValue,
19 } from "../HIR/HIR";
20 -import { eachInstructionValueOperand } from "../HIR/visitors";
20 +import {
21 + eachInstructionLValue,
22 + eachInstructionValueOperand,
23 +} from "../HIR/visitors";
24 import { assertExhaustive } from "../Utils/utils";
25
26 export function visitReactiveFunction<TState>(
@@ -69,8 +72,8 @@ export class ReactiveFunctionVisitor<TState = void> {
72 }
73 traverseInstruction(instruction: ReactiveInstruction, state: TState): void {
74 this.visitID(instruction.id, state);
72 - if (instruction.lvalue !== null) {
73 - this.visitLValue(instruction.id, instruction.lvalue, state);
75 + for (const operand of eachInstructionLValue(instruction)) {
76 + this.visitLValue(instruction.id, operand, state);
77 }
78 this.visitValue(instruction.id, instruction.value, state);
79 }
compiler/forget/src/SSA/EliminateRedundantPhi.ts
+8 -1
@@ -7,7 +7,11 @@
7
8 import invariant from "invariant";
9 import { BlockId, HIRFunction, Identifier, Place } from "../HIR/HIR";
10 -import { eachInstructionOperand, eachTerminalOperand } from "../HIR/visitors";
10 +import {
11 + eachInstructionLValue,
12 + eachInstructionOperand,
13 + eachTerminalOperand,
14 +} from "../HIR/visitors";
15
16 /**
17 * Pass to eliminate redundant phi nodes:
@@ -79,6 +83,9 @@ export function eliminateRedundantPhi(fn: HIRFunction) {
83
84 // Rewrite all instruction lvalues and operands
85 for (const instr of block.instructions) {
86 + for (const place of eachInstructionLValue(instr)) {
87 + rewritePlace(place, rewrites);
88 + }
89 for (const place of eachInstructionOperand(instr)) {
90 rewritePlace(place, rewrites);
91 }
compiler/forget/src/SSA/EnterSSA.ts
+3 -19
@@ -15,8 +15,8 @@ import {
15 import { printIdentifier } from "../HIR/PrintHIR";
16 import {
17 eachTerminalSuccessor,
18 + mapInstructionLValues,
19 mapInstructionOperands,
19 - mapPatternOperands,
20 mapTerminalOperands,
21 } from "../HIR/visitors";
22
@@ -224,24 +224,8 @@ export default function enterSSA(func: HIRFunction): void {
224 }
225
226 for (const instr of block.instructions) {
227 - if (instr.value.kind === "StoreLocal") {
228 - const oldPlace = instr.value.lvalue.place;
229 - const newPlace = builder.definePlace(oldPlace);
230 - instr.value.lvalue.place = newPlace;
231 -
232 - instr.value.value = builder.getPlace(instr.value.value);
233 - } else if (instr.value.kind === "Destructure") {
234 - mapPatternOperands(instr.value.lvalue.pattern, (place) =>
235 - builder.definePlace(place)
236 - );
237 - instr.value.value = builder.getPlace(instr.value.value);
238 - } else {
239 - mapInstructionOperands(instr, (place) => builder.getPlace(place));
240 - }
241 -
242 - const oldPlace = instr.lvalue;
243 - const newPlace = builder.definePlace(oldPlace);
244 - instr.lvalue = newPlace;
227 + mapInstructionLValues(instr, (lvalue) => builder.definePlace(lvalue));
228 + mapInstructionOperands(instr, (place) => builder.getPlace(place));
229 }
230
231 mapTerminalOperands(block.terminal, (place) => builder.getPlace(place));
compiler/forget/src/SSA/LeaveSSA.ts
+4
@@ -24,6 +24,7 @@ import {
24 } from "../HIR/HIR";
25 import { printPlace } from "../HIR/PrintHIR";
26 import {
27 + eachInstructionLValue,
28 eachInstructionValueOperand,
29 eachPatternOperand,
30 eachTerminalOperand,
@@ -188,6 +189,9 @@ export function leaveSSA(fn: HIRFunction): void {
189 value.lvalue.kind = kind;
190 }
191 rewritePlace(lvalue, rewrites, declarations);
192 + for (const operand of eachInstructionLValue(instr)) {
193 + rewritePlace(operand, rewrites, declarations);
194 + }
195 for (const operand of eachInstructionValueOperand(instr.value)) {
196 rewritePlace(operand, rewrites, declarations);
197 }
compiler/forget/src/TypeInference/InferTypes.ts
+4 -1
@@ -9,7 +9,7 @@ import {
9 TypeId,
10 TypeVar,
11 } from "../HIR/HIR";
12 -import { eachInstructionOperand } from "../HIR/visitors";
12 +import { eachInstructionLValue, eachInstructionOperand } from "../HIR/visitors";
13
14 function isPrimitiveBinaryOp(op: t.BinaryExpression["operator"]) {
15 switch (op) {
@@ -50,6 +50,9 @@ function apply(func: HIRFunction, unifier: Unifier) {
50 phi.type = unifier.get(phi.type);
51 }
52 for (const instr of block.instructions) {
53 + for (const operand of eachInstructionLValue(instr)) {
54 + operand.identifier.type = unifier.get(operand.identifier.type);
55 + }
56 for (const place of eachInstructionOperand(instr)) {
57 place.identifier.type = unifier.get(place.identifier.type);
58 }