@samitouri / QOS-React-2 / commits / 3d4130140a

[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 +}