[hir] Split value tracking into a separate pass
Sathya Gunasekaran committed
Dec 13, 2022 at 09:17 UTC
7f8ad491651fa2136288bf731dda79f5d1e8f3e3
2 files changed
+150
-118
compiler/forget/src/HIR/InferAlias.ts
+6
-118
@@ -3,97 +3,14 @@ import DisjointSet from "./DisjointSet";
3
import { HIRFunction, Identifier, Instruction, LValue, Place } 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
-
6
export type AliasSet = Set<Identifier>;
7
18
-class AbstractState {
8
+class AliasAnalyser {
9
aliases = new DisjointSet<Identifier>();
10
// NOTE(gsn): Should this be a part of AbstractObject? No, because this has
11
// nothing to do with values in the object.
12
objectAliases = new Map<Identifier, Map<string, AliasSet>>();
13
24
- #values = new Map<Identifier, AbstractValue>();
25
-
26
- read(alias: Place): AbstractValue {
27
- // Simple alias:
28
- // read(alias);
29
- if (alias.memberPath === null) {
30
- let value = this.#values.get(alias.identifier);
31
-
32
- // Don't know what this is, let's default to an Object conservatively.
33
- if (value === undefined) {
34
- value = { kind: "Object", values: new Map() };
35
- }
36
-
37
- this.#values.set(alias.identifier, value);
38
- return value;
39
- }
40
-
41
- if (alias.memberPath.length > 1) {
42
- // TODO(gsn): Correctly handle nested member paths when reading values
43
- return { kind: "Object", values: new Map() };
44
- }
45
-
46
- // Complex alias:
47
- // read(alias.memberPath);
48
- let object = this.#values.get(alias.identifier);
49
-
50
- // Don't know what this is, let's default to an Object conservatively.
51
- if (object === undefined) {
52
- object = { kind: "Object", values: new Map() };
53
- this.#values.set(alias.identifier, object);
54
- }
55
-
56
- // We're doing a member lookup on a non object.
57
- //
58
- // alias = 1;
59
- // read(alias.memberPath);
60
- if (object.kind !== "Object") {
61
- // Update alias to be an object
62
- object = { kind: "Object", values: new Map() };
63
- this.#values.set(alias.identifier, object);
64
-
65
- // Conservatively type the value as object.
66
- //
67
- // NOTE(gsn): Should this be an AbstractUnknown rather than an
68
- // AbstractObject?
69
- let value: AbstractObject = { kind: "Object", values: new Map() };
70
- object.values.set(alias.memberPath[0], value);
71
- return value;
72
- }
73
-
74
- let value = object.values.get(alias.memberPath[0]);
75
-
76
- // We don't have a value for this member path.
77
- //
78
- // alias = {};
79
- // read(alias.memberPath);
80
- if (value === undefined) {
81
- // Conservatively type the value as object.
82
- value = {
83
- kind: "Object",
84
- values: new Map(),
85
- };
86
- object.values.set(alias.memberPath[0], value);
87
- return value;
88
- }
89
-
90
- // We have a value for this memberPath!
91
- //
92
- // alias.memberPath = value;
93
- // read(alias.memberPath);
94
- return value;
95
- }
96
-
14
alias(lvalue: LValue, alias: Place) {
15
// Complex lvalue:
16
// lvalue.memberPath = alias;
@@ -141,66 +58,37 @@ class AbstractState {
58
// lvalue = alias.memberPath;
59
this.aliases.union([lvalue.place.identifier, alias.identifier]);
60
}
144
-
145
- store(lvalue: LValue, value: AbstractValue) {
146
- // TODO(gsn): Handle stores for complex lvalue
147
- if (lvalue.place.memberPath !== null) {
148
- return;
149
- }
150
-
151
- // Simple lvalue:
152
- // lvalue = alias;
153
- // lvalue = alias.memberPath;
154
- this.#values.set(lvalue.place.identifier, value);
155
- }
61
}
62
63
export function inferAliases(func: HIRFunction): DisjointSet<Identifier> {
159
- const state = new AbstractState();
64
+ const analyser = new AliasAnalyser();
65
for (const [_, block] of func.body.blocks) {
66
for (const instr of block.instructions) {
162
- inferInstr(instr, state);
67
+ inferInstr(instr, analyser);
68
}
69
}
70
166
- return state.aliases;
71
+ return analyser.aliases;
72
}
73
169
-function inferInstr(instr: Instruction, state: AbstractState) {
74
+function inferInstr(instr: Instruction, state: AliasAnalyser) {
75
const { lvalue, value: instrValue } = instr;
76
let alias: Place | null = null;
172
- let value: AbstractValue | null = null;
77
switch (instrValue.kind) {
174
- case "Primitive": {
175
- value = {
176
- kind: "Primitive",
177
- value: instrValue.value,
178
- };
179
- break;
180
- }
78
case "Identifier": {
79
alias = instrValue;
183
- value = state.read(alias);
80
break;
81
}
82
default:
83
return;
84
}
85
190
- invariant(
191
- value !== null,
192
- `expected ${printInstructionValue(instrValue)} to be an alias or value`
193
- );
194
-
86
// TODO(gsn): handle this.
87
if (lvalue === null) {
88
return;
89
}
90
200
- // No need to alias Primitives.
201
- if (alias !== null && value.kind !== "Primitive") {
91
+ if (alias !== null) {
92
state.alias(lvalue, alias);
93
}
204
-
205
- state.store(lvalue, value);
94
}
compiler/forget/src/HIR/InferTypes.ts
new
+144
@@ -0,0 +1,144 @@
1
+import invariant from "invariant";
2
+import DisjointSet from "./DisjointSet";
3
+import { HIRFunction, Identifier, Instruction, LValue, Place } 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
+ #values = new Map<Identifier, AbstractValue>();
18
+
19
+ read(alias: Place): AbstractValue {
20
+ // Simple alias:
21
+ // read(alias);
22
+ if (alias.memberPath === null) {
23
+ let value = this.#values.get(alias.identifier);
24
+
25
+ // Don't know what this is, let's default to an Object conservatively.
26
+ if (value === undefined) {
27
+ value = { kind: "Object", values: new Map() };
28
+ }
29
+
30
+ this.#values.set(alias.identifier, value);
31
+ return value;
32
+ }
33
+
34
+ if (alias.memberPath.length > 1) {
35
+ // TODO(gsn): Correctly handle nested member paths when reading values
36
+ return { kind: "Object", values: new Map() };
37
+ }
38
+
39
+ // Complex alias:
40
+ // read(alias.memberPath);
41
+ let object = this.#values.get(alias.identifier);
42
+
43
+ // Don't know what this is, let's default to an Object conservatively.
44
+ if (object === undefined) {
45
+ object = { kind: "Object", values: new Map() };
46
+ this.#values.set(alias.identifier, object);
47
+ }
48
+
49
+ // We're doing a member lookup on a non object.
50
+ //
51
+ // alias = 1;
52
+ // read(alias.memberPath);
53
+ if (object.kind !== "Object") {
54
+ // Update alias to be an object
55
+ object = { kind: "Object", values: new Map() };
56
+ this.#values.set(alias.identifier, object);
57
+
58
+ // Conservatively type the value as object.
59
+ //
60
+ // NOTE(gsn): Should this be an AbstractUnknown rather than an
61
+ // AbstractObject?
62
+ let value: AbstractObject = { kind: "Object", values: new Map() };
63
+ object.values.set(alias.memberPath[0], value);
64
+ return value;
65
+ }
66
+
67
+ let value = object.values.get(alias.memberPath[0]);
68
+
69
+ // We don't have a value for this member path.
70
+ //
71
+ // alias = {};
72
+ // read(alias.memberPath);
73
+ if (value === undefined) {
74
+ // Conservatively type the value as object.
75
+ value = {
76
+ kind: "Object",
77
+ values: new Map(),
78
+ };
79
+ object.values.set(alias.memberPath[0], value);
80
+ return value;
81
+ }
82
+
83
+ // We have a value for this memberPath!
84
+ //
85
+ // alias.memberPath = value;
86
+ // read(alias.memberPath);
87
+ return value;
88
+ }
89
+
90
+ store(lvalue: LValue, value: AbstractValue) {
91
+ // TODO(gsn): Handle stores for complex lvalue
92
+ if (lvalue.place.memberPath !== null) {
93
+ return;
94
+ }
95
+
96
+ // Simple lvalue:
97
+ // lvalue = alias;
98
+ // lvalue = alias.memberPath;
99
+ this.#values.set(lvalue.place.identifier, value);
100
+ }
101
+}
102
+
103
+export function inferTypes(func: HIRFunction) {
104
+ const state = new AbstractState();
105
+ for (const [_, block] of func.body.blocks) {
106
+ for (const instr of block.instructions) {
107
+ inferInstr(instr, state);
108
+ }
109
+ }
110
+}
111
+
112
+function inferInstr(instr: Instruction, state: AbstractState) {
113
+ const { lvalue, value: instrValue } = instr;
114
+ let alias: Place | null = null;
115
+ let value: AbstractValue | null = null;
116
+ switch (instrValue.kind) {
117
+ case "Primitive": {
118
+ value = {
119
+ kind: "Primitive",
120
+ value: instrValue.value,
121
+ };
122
+ break;
123
+ }
124
+ case "Identifier": {
125
+ alias = instrValue;
126
+ value = state.read(alias);
127
+ break;
128
+ }
129
+ default:
130
+ return;
131
+ }
132
+
133
+ invariant(
134
+ value !== null,
135
+ `expected ${printInstructionValue(instrValue)} to be an alias or value`
136
+ );
137
+
138
+ // TODO(gsn): handle this.
139
+ if (lvalue === null) {
140
+ return;
141
+ }
142
+
143
+ state.store(lvalue, value);
144
+}