[new-arch] Scaffolding and fixtures for mutability lifetime inference
Scaffolds out mutable lifetime inference and the potential two-pass approach, with motivating examples. Also adds some fixtures that collectively demonstrate a bunch of cases of aliasing: * direct assignment `a = b` * property assignment `a.b = b` * array literals `a = [b]` * object literals `a = {b}` * mutable arguments to the same call `foo(mut a, mut b)` * return values aliasing arguments `a = foo(mut b)` * aliasing that occurs only after multiple loop iterations All of these fixtures use an empty `if (varName) {}` as a way to check that an otherwise readonly usage of a variable is correctly inferred as mutable.
Joseph Savona committed
Oct 12, 2022 at 10:54 UTC
3d4130140ab6c0dbf88a6898e77a562442d12892
5 files changed
+456
compiler/forget/src/HIR/InferMutableLifetimes.ts
new
+52
@@ -0,0 +1,52 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import { HIRFunction } from "./HIR";
9
+
10
+/**
11
+ * For each usage of a value in the given function, determines if the usage
12
+ * may be succeeded by a mutable usage of that same value and if so updates
13
+ * the usage to be mutable.
14
+ *
15
+ * Stated differently, this inference ensures that inferred capabilities of
16
+ * each reference are as follows:
17
+ * - freeze: the value is frozen at this point
18
+ * - readonly: the value is not modified at this point *or any subsequent
19
+ * point*
20
+ * - mutable: the value is modified at this point *or some subsequent point*.
21
+ *
22
+ * Note that this refines the capabilities inferered by InferReferenceCapability,
23
+ * which looks at individual references and not the lifetime of a value's mutability.
24
+ *
25
+ * == Algorithm
26
+ *
27
+ * 1. Forward data-flow analysis to determine aliasing. Unlike InferReferenceCapability
28
+ * which only tracks aliasing of top-level variables (`y = x`), this analysis needs
29
+ * to know if a value is aliased anywhere (`y.x = x`). The forward data flow tracks
30
+ * all possible locations which may have aliased a value. The concrete result is
31
+ * a mapping of each Place to the set of possibly-mutable values it may alias.
32
+ *
33
+ * ```
34
+ * const x = []; // {x: v0; v0: mutable []}
35
+ * const y = {}; // {x: v0, y: v1; v0: mutable [], v1: mutable []}
36
+ * y.x = x; // {x: v0, y: v1; v0: mutable [v1], v1: mutable [v0]}
37
+ * read(x); // {x: v0, y: v1; v0: mutable [v1], v1: mutable [v0]}
38
+ * mutate(y); // can infer that y mutates v0 and v1
39
+ * ```
40
+ *
41
+ * 2. Backward data-flow analysis to compute mutability liveness. Walk backwards over
42
+ * the CFG and track which values are mutated in a successor. Then when visiting
43
+ * preceding statements, mark any reference to a value that is known mutated as
44
+ * mutable.
45
+ *
46
+ * ```
47
+ * mutate(y); // mutable y => v0, v1 mutated
48
+ * read(x); // x maps to v0, v1, those are in the mutated-later set, so x is mutable here
49
+ * ...
50
+ * ```
51
+ */
52
+function inferMutableLifetimes(fn: HIRFunction) {}
compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.expect.md
new
+119
@@ -0,0 +1,119 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function mutate(x, y) {}
6
+function cond(x) {}
7
+
8
+function Component(props) {
9
+ let a = {};
10
+ let b = {};
11
+ let c = {};
12
+ let d = {};
13
+ while (true) {
14
+ let z = a;
15
+ a = b;
16
+ b = c;
17
+ c = d;
18
+ d = z;
19
+ mutate(a, b);
20
+ if (cond(a)) {
21
+ break;
22
+ }
23
+ }
24
+
25
+ // all of these tests are seemingly readonly, since the values are never directly
26
+ // mutated again. but they are all aliased by `d`, which is later modified, and
27
+ // these are therefore mutable references:
28
+ if (a) {
29
+ }
30
+ if (b) {
31
+ }
32
+ if (c) {
33
+ }
34
+ if (d) {
35
+ }
36
+
37
+ mutate(d, null);
38
+}
39
+
40
+```
41
+
42
+## HIR
43
+
44
+```
45
+bb0:
46
+ Return
47
+```
48
+
49
+## Code
50
+
51
+```javascript
52
+function mutate$0(x$1, y$2) {
53
+ return;
54
+}
55
+
56
+```
57
+## HIR
58
+
59
+```
60
+bb0:
61
+ Return
62
+```
63
+
64
+## Code
65
+
66
+```javascript
67
+function cond$0(x$1) {
68
+ return;
69
+}
70
+
71
+```
72
+## HIR
73
+
74
+```
75
+bb0:
76
+ Let mutable a$2 = Object { }
77
+ Let mutable b$3 = Object { }
78
+ Let mutable c$4 = Object { }
79
+ Let mutable d$5 = Object { }
80
+ Goto bb1
81
+bb1:
82
+ Const mutable $10 = true
83
+ If (readonly $10) then:bb3 else:bb2
84
+bb3:
85
+ Let mutable z$6 = readonly a$2
86
+ Reassign mutable a$2 = readonly b$3
87
+ Reassign mutable b$3 = readonly c$4
88
+ Reassign mutable c$4 = readonly d$5
89
+ Reassign mutable d$5 = readonly z$6
90
+ Call mutable mutate$7(mutable a$2, mutable b$3)
91
+ Const mutable $9 = Call mutable cond$8(mutable a$2)
92
+ If (readonly $9) then:bb2 else:bb1
93
+bb2:
94
+ If (readonly a$2) then:bb7 else:bb7
95
+bb7:
96
+ If (readonly b$3) then:bb9 else:bb9
97
+bb9:
98
+ If (readonly c$4) then:bb11 else:bb11
99
+bb11:
100
+ If (readonly d$5) then:bb13 else:bb13
101
+bb13:
102
+ Const mutable $11 = null
103
+ Call mutable mutate$7(mutable d$5, readonly $11)
104
+ Return
105
+```
106
+
107
+## Code
108
+
109
+```javascript
110
+function Component$0(props$1) {
111
+ let a$2 = {};
112
+ let b$3 = {};
113
+ let c$4 = {};
114
+ let d$5 = {};
115
+ ("<<TODO: handle complex control flow in codegen>>");
116
+}
117
+
118
+```
119
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.js
new
+34
@@ -0,0 +1,34 @@
1
+function mutate(x, y) {}
2
+function cond(x) {}
3
+
4
+function Component(props) {
5
+ let a = {};
6
+ let b = {};
7
+ let c = {};
8
+ let d = {};
9
+ while (true) {
10
+ let z = a;
11
+ a = b;
12
+ b = c;
13
+ c = d;
14
+ d = z;
15
+ mutate(a, b);
16
+ if (cond(a)) {
17
+ break;
18
+ }
19
+ }
20
+
21
+ // all of these tests are seemingly readonly, since the values are never directly
22
+ // mutated again. but they are all aliased by `d`, which is later modified, and
23
+ // these are therefore mutable references:
24
+ if (a) {
25
+ }
26
+ if (b) {
27
+ }
28
+ if (c) {
29
+ }
30
+ if (d) {
31
+ }
32
+
33
+ mutate(d, null);
34
+}
compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-with-aliasing.expect.md
new
+221
@@ -0,0 +1,221 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function mutate(x, y) {}
6
+
7
+function Component(props) {
8
+ const a = {};
9
+ const b = [a]; // array elements alias
10
+ const c = {};
11
+ const d = { c }; // object values alias
12
+
13
+ // capture all the values into this object
14
+ const x = {};
15
+ x.b = b;
16
+ const y = mutate(x, d); // mutation aliases the arg and return value
17
+
18
+ // all of these tests are seemingly readonly, since the values are never directly
19
+ // mutated again. but they are all aliased by `x`, which is later modified, and
20
+ // these are therefore mutable references:
21
+ if (a) {
22
+ }
23
+ if (b) {
24
+ }
25
+ if (c) {
26
+ }
27
+ if (d) {
28
+ }
29
+ if (y) {
30
+ }
31
+
32
+ // could in theory mutate any of a/b/c/x/z, so the above should be inferred as mutable
33
+ mutate(x, null);
34
+}
35
+
36
+```
37
+
38
+## HIR
39
+
40
+```
41
+bb0:
42
+ Return
43
+```
44
+
45
+## Code
46
+
47
+```javascript
48
+function mutate$0(x$1, y$2) {
49
+ return;
50
+}
51
+
52
+```
53
+## HIR
54
+
55
+```
56
+bb0:
57
+ Const mutable a$2 = Object { }
58
+ Const mutable b$3 = Array [readonly a$2]
59
+ Const mutable c$4 = Object { }
60
+ Const mutable d$5 = Object { c: readonly c$4 }
61
+ Const mutable x$6 = Object { }
62
+ Reassign mutable x$6.b = readonly b$3
63
+ Const mutable y$7 = Call mutable mutate$8(mutable x$6, mutable d$5)
64
+ If (readonly a$2) then:bb1 else:bb1
65
+bb1:
66
+ If (readonly b$3) then:bb3 else:bb3
67
+bb3:
68
+ If (readonly c$4) then:bb5 else:bb5
69
+bb5:
70
+ If (readonly d$5) then:bb7 else:bb7
71
+bb7:
72
+ If (readonly y$7) then:bb9 else:bb9
73
+bb9:
74
+ Const mutable $9 = null
75
+ Call mutable mutate$8(mutable x$6, readonly $9)
76
+ Return
77
+```
78
+
79
+## Code
80
+
81
+```javascript
82
+function Component$0(props$1) {
83
+ const a$2 = {};
84
+ const b$3 = [a$2];
85
+ const c$4 = {};
86
+ const d$5 = {
87
+ c: c$4,
88
+ };
89
+ const x$6 = {};
90
+ x$6 = b$3;
91
+ const y$7 = mutate$8(x$6, d$5);
92
+ if (a$2) {
93
+ if (b$3) {
94
+ if (c$4) {
95
+ if (d$5) {
96
+ if (y$7) {
97
+ mutate$8(x$6, null);
98
+ return;
99
+ }
100
+ mutate$8(x$6, null);
101
+ return;
102
+ }
103
+ if (y$7) {
104
+ mutate$8(x$6, null);
105
+ return;
106
+ }
107
+ mutate$8(x$6, null);
108
+ return;
109
+ }
110
+ if (d$5) {
111
+ if (y$7) {
112
+ mutate$8(x$6, null);
113
+ return;
114
+ }
115
+ mutate$8(x$6, null);
116
+ return;
117
+ }
118
+ if (y$7) {
119
+ mutate$8(x$6, null);
120
+ return;
121
+ }
122
+ mutate$8(x$6, null);
123
+ return;
124
+ }
125
+ if (c$4) {
126
+ if (d$5) {
127
+ if (y$7) {
128
+ mutate$8(x$6, null);
129
+ return;
130
+ }
131
+ mutate$8(x$6, null);
132
+ return;
133
+ }
134
+ if (y$7) {
135
+ mutate$8(x$6, null);
136
+ return;
137
+ }
138
+ mutate$8(x$6, null);
139
+ return;
140
+ }
141
+ if (d$5) {
142
+ if (y$7) {
143
+ mutate$8(x$6, null);
144
+ return;
145
+ }
146
+ mutate$8(x$6, null);
147
+ return;
148
+ }
149
+ if (y$7) {
150
+ mutate$8(x$6, null);
151
+ return;
152
+ }
153
+ mutate$8(x$6, null);
154
+ return;
155
+ }
156
+ if (b$3) {
157
+ if (c$4) {
158
+ if (d$5) {
159
+ if (y$7) {
160
+ mutate$8(x$6, null);
161
+ return;
162
+ }
163
+ mutate$8(x$6, null);
164
+ return;
165
+ }
166
+ if (y$7) {
167
+ mutate$8(x$6, null);
168
+ return;
169
+ }
170
+ mutate$8(x$6, null);
171
+ return;
172
+ }
173
+ if (d$5) {
174
+ if (y$7) {
175
+ mutate$8(x$6, null);
176
+ return;
177
+ }
178
+ mutate$8(x$6, null);
179
+ return;
180
+ }
181
+ if (y$7) {
182
+ mutate$8(x$6, null);
183
+ return;
184
+ }
185
+ mutate$8(x$6, null);
186
+ return;
187
+ }
188
+ if (c$4) {
189
+ if (d$5) {
190
+ if (y$7) {
191
+ mutate$8(x$6, null);
192
+ return;
193
+ }
194
+ mutate$8(x$6, null);
195
+ return;
196
+ }
197
+ if (y$7) {
198
+ mutate$8(x$6, null);
199
+ return;
200
+ }
201
+ mutate$8(x$6, null);
202
+ return;
203
+ }
204
+ if (d$5) {
205
+ if (y$7) {
206
+ mutate$8(x$6, null);
207
+ return;
208
+ }
209
+ mutate$8(x$6, null);
210
+ return;
211
+ }
212
+ if (y$7) {
213
+ mutate$8(x$6, null);
214
+ return;
215
+ }
216
+ mutate$8(x$6, null);
217
+ return;
218
+}
219
+
220
+```
221
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-with-aliasing.js
new
+30
@@ -0,0 +1,30 @@
1
+function mutate(x, y) {}
2
+
3
+function Component(props) {
4
+ const a = {};
5
+ const b = [a]; // array elements alias
6
+ const c = {};
7
+ const d = { c }; // object values alias
8
+
9
+ // capture all the values into this object
10
+ const x = {};
11
+ x.b = b;
12
+ const y = mutate(x, d); // mutation aliases the arg and return value
13
+
14
+ // all of these tests are seemingly readonly, since the values are never directly
15
+ // mutated again. but they are all aliased by `x`, which is later modified, and
16
+ // these are therefore mutable references:
17
+ if (a) {
18
+ }
19
+ if (b) {
20
+ }
21
+ if (c) {
22
+ }
23
+ if (d) {
24
+ }
25
+ if (y) {
26
+ }
27
+
28
+ // could in theory mutate any of a/b/c/x/z, so the above should be inferred as mutable
29
+ mutate(x, null);
30
+}