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

[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": {