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
}