[hir] Introduce ValueKind.Context
This tracks whether a value is a context ref or generated from a context ref. This lets us track mutations to context refs and treat it separately as we want this to be more conservative than our existing inference. ValueKind.Context is exactly like ValueKind.Mutable but is more conservative.
Sathya Gunasekaran committed
Feb 10, 2023 at 17:59 UTC
fa57551dd51d6d3ad38b0a0bdb7543b41726c5dc
10 files changed
+234
-4
compiler/forget/src/HIR/HIR.ts
+1
@@ -531,6 +531,7 @@ export enum ValueKind {
531
Frozen = "frozen",
532
Immutable = "immutable",
533
Mutable = "mutable",
534
+ Context = "context",
535
}
536
537
/**
compiler/forget/src/Inference/InferReferenceEffects.ts
+31
-4
@@ -26,6 +26,7 @@ import {
26
} from "../HIR/PrintHIR";
27
import {
28
eachInstructionOperand,
29
+ eachInstructionValueOperand,
30
eachTerminalOperand,
31
eachTerminalSuccessor,
32
} from "../HIR/visitors";
@@ -100,7 +101,7 @@ export default function inferReferenceEffects(fn: HIRFunction) {
101
properties: null,
102
loc: ref.loc,
103
};
103
- initialEnvironment.initialize(value, ValueKind.Mutable);
104
+ initialEnvironment.initialize(value, ValueKind.Context);
105
initialEnvironment.define(ref, value);
106
}
107
@@ -274,6 +275,7 @@ class Environment {
275
case Effect.Freeze: {
276
if (
277
valueKind === ValueKind.Mutable ||
278
+ valueKind === ValueKind.Context ||
279
valueKind === ValueKind.MaybeFrozen
280
) {
281
effect = Effect.Freeze;
@@ -285,7 +287,10 @@ class Environment {
287
break;
288
}
289
case Effect.Mutate: {
288
- if (valueKind === ValueKind.Mutable) {
290
+ if (
291
+ valueKind === ValueKind.Mutable ||
292
+ valueKind === ValueKind.Context
293
+ ) {
294
effect = Effect.Mutate;
295
} else {
296
effect = Effect.Read;
@@ -488,6 +493,9 @@ class Environment {
493
* types. To ensure that any sequence of joins btw those three states yields the
494
* correct maybe-frozen, these two have to produce a frozen value.
495
* - <any> | maybe-frozen => maybe-frozen
496
+ * - immutable | context => context
497
+ * - mutable | context => context
498
+ * - frozen | context => maybe-frozen
499
*
500
* ┌──────────────────────────┐
501
* │ Immutable │───┐
@@ -513,10 +521,21 @@ function mergeValues(a: ValueKind, b: ValueKind): ValueKind {
521
if (a === ValueKind.Frozen || b === ValueKind.Frozen) {
522
// frozen | mutable
523
return ValueKind.MaybeFrozen;
524
+ } else if (a === ValueKind.Context || b === ValueKind.Context) {
525
+ // context | mutable
526
+ return ValueKind.Context;
527
} else {
528
// mutable | immutable
529
return ValueKind.Mutable;
530
}
531
+ } else if (a === ValueKind.Context || b === ValueKind.Context) {
532
+ if (a === ValueKind.Frozen || b === ValueKind.Frozen) {
533
+ // frozen | context
534
+ return ValueKind.MaybeFrozen;
535
+ } else {
536
+ // context | immutable
537
+ return ValueKind.Context;
538
+ }
539
} else {
540
// frozen | immutable
541
return ValueKind.Frozen;
@@ -664,7 +683,11 @@ function inferBlock(env: Environment, block: BasicBlock) {
683
continue;
684
}
685
case "PropertyStore": {
667
- env.reference(instrValue.value, Effect.Capture);
686
+ const effect =
687
+ env.kind(instrValue.object) === ValueKind.Context
688
+ ? Effect.Mutate
689
+ : Effect.Capture;
690
+ env.reference(instrValue.value, effect);
691
env.reference(instrValue.object, Effect.Store);
692
693
const lvalue = instr.lvalue;
@@ -692,7 +715,11 @@ function inferBlock(env: Environment, block: BasicBlock) {
715
continue;
716
}
717
case "ComputedStore": {
695
- env.reference(instrValue.value, Effect.Capture);
718
+ const effect =
719
+ env.kind(instrValue.object) === ValueKind.Context
720
+ ? Effect.Mutate
721
+ : Effect.Capture;
722
+ env.reference(instrValue.value, effect);
723
env.reference(instrValue.property, Effect.Capture);
724
env.reference(instrValue.object, Effect.Store);
725
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component(a) {
6
+ let x = { a };
7
+ let y;
8
+ (function () {
9
+ y["x"] = x;
10
+ })();
11
+ mutate(y);
12
+ return y;
13
+}
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+function component(a) {
21
+ const $ = React.unstable_useMemoCache();
22
+ const c_0 = $[0] !== a;
23
+ let y;
24
+ if (c_0) {
25
+ const x = { a: a };
26
+ y = undefined;
27
+ (function () {
28
+ y["x"] = x;
29
+ })();
30
+ $[0] = a;
31
+ $[1] = y;
32
+ } else {
33
+ y = $[1];
34
+ }
35
+ mutate(y);
36
+ return y;
37
+}
38
+
39
+```
40
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.js
new
+9
@@ -0,0 +1,9 @@
1
+function component(a) {
2
+ let x = { a };
3
+ let y;
4
+ (function () {
5
+ y["x"] = x;
6
+ })();
7
+ mutate(y);
8
+ return y;
9
+}
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component(a) {
6
+ let x = { a };
7
+ let y;
8
+ (function () {
9
+ y.x = x;
10
+ })();
11
+ mutate(y);
12
+ return y;
13
+}
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+function component(a) {
21
+ const $ = React.unstable_useMemoCache();
22
+ const c_0 = $[0] !== a;
23
+ let y;
24
+ if (c_0) {
25
+ const x = { a: a };
26
+ y = undefined;
27
+ (function () {
28
+ y.x = x;
29
+ })();
30
+ $[0] = a;
31
+ $[1] = y;
32
+ } else {
33
+ y = $[1];
34
+ }
35
+ mutate(y);
36
+ return y;
37
+}
38
+
39
+```
40
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.js
new
+9
@@ -0,0 +1,9 @@
1
+function component(a) {
2
+ let x = { a };
3
+ let y;
4
+ (function () {
5
+ y.x = x;
6
+ })();
7
+ mutate(y);
8
+ return y;
9
+}
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-receiver-computed-mutate.expect.md
new
+42
@@ -0,0 +1,42 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component(a) {
6
+ let x = { a };
7
+ let y = {};
8
+ (function () {
9
+ let a = y;
10
+ a["x"] = x;
11
+ })();
12
+ mutate(y);
13
+ return y;
14
+}
15
+
16
+```
17
+
18
+## Code
19
+
20
+```javascript
21
+function component(a) {
22
+ const $ = React.unstable_useMemoCache();
23
+ const c_0 = $[0] !== a;
24
+ let y;
25
+ if (c_0) {
26
+ const x = { a: a };
27
+ y = {};
28
+ (function () {
29
+ let a = y;
30
+ a["x"] = x;
31
+ })();
32
+ mutate(y);
33
+ $[0] = a;
34
+ $[1] = y;
35
+ } else {
36
+ y = $[1];
37
+ }
38
+ return y;
39
+}
40
+
41
+```
42
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-receiver-computed-mutate.js
new
+10
@@ -0,0 +1,10 @@
1
+function component(a) {
2
+ let x = { a };
3
+ let y = {};
4
+ (function () {
5
+ let a = y;
6
+ a["x"] = x;
7
+ })();
8
+ mutate(y);
9
+ return y;
10
+}
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-receiver-mutate.expect.md
new
+42
@@ -0,0 +1,42 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component(a) {
6
+ let x = { a };
7
+ let y = {};
8
+ (function () {
9
+ let a = y;
10
+ a.x = x;
11
+ })();
12
+ mutate(y);
13
+ return y;
14
+}
15
+
16
+```
17
+
18
+## Code
19
+
20
+```javascript
21
+function component(a) {
22
+ const $ = React.unstable_useMemoCache();
23
+ const c_0 = $[0] !== a;
24
+ let y;
25
+ if (c_0) {
26
+ const x = { a: a };
27
+ y = {};
28
+ (function () {
29
+ let a = y;
30
+ a.x = x;
31
+ })();
32
+ mutate(y);
33
+ $[0] = a;
34
+ $[1] = y;
35
+ } else {
36
+ y = $[1];
37
+ }
38
+ return y;
39
+}
40
+
41
+```
42
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-receiver-mutate.js
new
+10
@@ -0,0 +1,10 @@
1
+function component(a) {
2
+ let x = { a };
3
+ let y = {};
4
+ (function () {
5
+ let a = y;
6
+ a.x = x;
7
+ })();
8
+ mutate(y);
9
+ return y;
10
+}