[hir] Add a buildAliasSets pass
Moves the existing alias set building logic from inferMutableLifetimes to a separate pass. Additionally maintain abstract state to refine aliasing to not include primitives.
Sathya Gunasekaran committed
Dec 1, 2022 at 19:03 UTC
c96c9b458fafb67f26e4fd240889a34cbeea7159
3 files changed
+114
-50
compiler/forget/src/HIR/BuildAliasSets.ts
new
+102
@@ -0,0 +1,102 @@
1
+import invariant from "invariant";
2
+import DisjointSet from "./DisjointSet";
3
+import { HIRFunction, Identifier, Instruction, Place, LValue } from "./HIR";
4
+import { printInstructionValue } from "./PrintHIR";
5
+
6
+type AbstractValue = AbstractObject | AbstractPrimitive;
7
+type AbstractObject = {
8
+ kind: "Object";
9
+ values: Map<string, AbstractValue>;
10
+};
11
+type AbstractPrimitive = {
12
+ kind: "Primitive";
13
+ value: number | boolean | string | null | undefined;
14
+};
15
+
16
+class AbstractState {
17
+ aliases = new DisjointSet<Identifier>();
18
+ #values = new Map<Identifier, AbstractValue>();
19
+
20
+ // Simple lvalue:
21
+ // lvalue = alias;
22
+ // lvalue = alias.memberPath;
23
+ alias(lvalue: LValue, alias: Place) {
24
+ // Simple alias:
25
+ // lvalue = alias;
26
+ if (alias.memberPath === null) {
27
+ let value = this.#values.get(alias.identifier);
28
+
29
+ // Don't know what this, let's default to an Object conservatively.
30
+ if (value === undefined) {
31
+ value = { kind: "Object", values: new Map() };
32
+ }
33
+
34
+ this.#values.set(lvalue.place.identifier, value);
35
+
36
+ // No need to alias Primitives
37
+ if (value.kind !== "Primitive") {
38
+ this.aliases.union([lvalue.place.identifier, alias.identifier]);
39
+ }
40
+ }
41
+ }
42
+
43
+ buildAliasSets(): Array<Set<Identifier>> {
44
+ const aliasIds: Map<Identifier, number> = new Map();
45
+ const aliasSets: Map<number, Set<Identifier>> = new Map();
46
+
47
+ this.aliases.forEach((identifier, groupIdentifier) => {
48
+ let aliasId = aliasIds.get(groupIdentifier);
49
+ if (aliasId == null) {
50
+ aliasId = aliasIds.size;
51
+ aliasIds.set(groupIdentifier, aliasId);
52
+ }
53
+
54
+ let aliasSet = aliasSets.get(aliasId);
55
+ if (aliasSet === undefined) {
56
+ aliasSet = new Set();
57
+ aliasSets.set(aliasId, aliasSet);
58
+ }
59
+ aliasSet.add(identifier);
60
+ });
61
+
62
+ return [...aliasSets.values()];
63
+ }
64
+}
65
+
66
+export function buildAliasSets(func: HIRFunction): Array<Set<Identifier>> {
67
+ const state = new AbstractState();
68
+ for (const [_, block] of func.body.blocks) {
69
+ for (const instr of block.instructions) {
70
+ inferInstr(instr, state);
71
+ }
72
+ }
73
+ return state.buildAliasSets();
74
+}
75
+
76
+function inferInstr(instr: Instruction, state: AbstractState) {
77
+ const { lvalue, value: instrValue } = instr;
78
+ let alias: Place | null = null;
79
+ switch (instrValue.kind) {
80
+ case "Identifier": {
81
+ alias = instrValue;
82
+ break;
83
+ }
84
+ default:
85
+ return;
86
+ }
87
+
88
+ invariant(
89
+ alias !== null,
90
+ `expected ${printInstructionValue(instrValue)} to have an alias`
91
+ );
92
+
93
+ // TODO(gsn): handle this.
94
+ if (lvalue === null) {
95
+ return;
96
+ }
97
+
98
+ // simple aliasing
99
+ if (lvalue.place.memberPath === null) {
100
+ state.alias(lvalue, alias);
101
+ }
102
+}
compiler/forget/src/HIR/InferMutableLifetimes.ts
+11
-49
@@ -7,6 +7,7 @@
7
8
import invariant from "invariant";
9
import { assertExhaustive } from "../Common/utils";
10
+import { buildAliasSets } from "./BuildAliasSets";
11
import DisjointSet from "./DisjointSet";
12
import {
13
Effect,
@@ -85,8 +86,6 @@ function inferPlace(place: Place, instr: Instruction) {
86
}
87
88
export function inferMutableRanges(func: HIRFunction) {
88
- const aliases = new DisjointSet<Identifier>();
89
-
89
for (const [_, block] of func.body.blocks) {
90
for (const phi of block.phis) {
91
let start = Number.MAX_SAFE_INTEGER;
@@ -111,20 +110,6 @@ export function inferMutableRanges(func: HIRFunction) {
110
}
111
112
if (instr.lvalue !== null) {
114
- if (instr.value.kind === "Identifier") {
115
- // TODO(gsn): Handle complex aliasing.
116
- if (
117
- instr.value.memberPath === null &&
118
- instr.lvalue.place.memberPath === null
119
- ) {
120
- // direct aliasing: `a = b`;
121
- aliases.union([
122
- instr.lvalue.place.identifier,
123
- instr.value.identifier,
124
- ]);
125
- }
126
- }
127
-
113
if (instr.lvalue.place.memberPath === null) {
114
const lvalueId = instr.lvalue.place.identifier;
115
@@ -142,45 +127,22 @@ export function inferMutableRanges(func: HIRFunction) {
127
}
128
}
129
145
- const aliasIds: Map<Identifier, number> = new Map();
146
- // Store the mutable range and set of identifiers for each scope
147
- const aliasIndentifiers: Map<
148
- number,
149
- { end: InstructionId; identifiers: Set<Identifier> }
150
- > = new Map();
151
-
152
- aliases.forEach((identifier, groupIdentifier) => {
153
- let aliasId = aliasIds.get(groupIdentifier);
154
- if (aliasId == null) {
155
- aliasId = aliasIds.size;
156
- aliasIds.set(groupIdentifier, aliasId);
157
- }
158
-
159
- let alias = aliasIndentifiers.get(aliasId);
160
- if (alias === undefined) {
161
- alias = {
162
- end: identifier.mutableRange.end,
163
- identifiers: new Set(),
164
- };
165
- aliasIndentifiers.set(aliasId, alias);
166
- } else {
167
- alias.end = makeInstructionId(
168
- Math.max(alias.end, identifier.mutableRange.end)
169
- );
170
- }
171
- alias.identifiers.add(identifier);
172
- });
173
-
174
- for (const [_, alias] of aliasIndentifiers) {
130
+ const aliasSets = buildAliasSets(func);
131
+ for (const aliasSet of aliasSets) {
132
// Update mutableRange.end only if the identifiers have actually been
133
// mutated.
177
- const haveIdentifiersBeenMutated = [...alias.identifiers].some(
134
+ const haveIdentifiersBeenMutated = [...aliasSet].some(
135
(id) => id.mutableRange.end > id.mutableRange.start
136
);
137
138
if (haveIdentifiersBeenMutated) {
182
- for (const identifier of alias.identifiers) {
183
- identifier.mutableRange.end = alias.end;
139
+ // Find final instruction which mutates this alias set.
140
+ const mutableRangeEnds = [...aliasSet].map((id) => id.mutableRange.end);
141
+ const maxMutableRangeEnd = Math.max(...mutableRangeEnds) as InstructionId;
142
+
143
+ // Update mutableRange.end for all aliases in this set.
144
+ for (const alias of aliasSet) {
145
+ alias.mutableRange.end = maxMutableRangeEnd;
146
}
147
}
148
}
compiler/forget/src/HIR/PrintHIR.ts
+1
-1
@@ -175,7 +175,7 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
175
return value;
176
}
177
178
-function printInstructionValue(instrValue: InstructionValue): string {
178
+export function printInstructionValue(instrValue: InstructionValue): string {
179
let value = "";
180
switch (instrValue.kind) {
181
case "ArrayExpression": {