Fix for invalid mutable range in phi with backedge
Fixes the repro added in 947832009997bf9149e88e583c46cc39f6a6136c - previously when computing mutable ranges of phis, we didn't check that all operands had been visited. This meant that a backedge could allow a phi's mutable range to start at 0. Then in PropagateScopeDeps, we might see reject dependencies of a scope since they appeared to start after a scope — only because the scope's start was incorrectly too early. The fix here is to initially set phi.id mutable ranges based on only on operands that are already visited. Then, during/after the fixpoint iteration of InferMutableRanges, we start account for all operands since we know they've been visited at least once and have a real range.
Joe Savona committed
Mar 25, 2024 at 21:36 UTC
fbd68eef7f8fad5dd1fdceb0fb79162c3d972e59
5 files changed
+93
-41
compiler/packages/babel-plugin-react-forget/src/Inference/InferMutableLifetimes.ts
+22
-15
@@ -5,7 +5,6 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { CompilerError } from "../CompilerError";
8
import {
9
Effect,
10
HIRFunction,
@@ -102,22 +101,30 @@ export function inferMutableLifetimes(
101
): void {
102
for (const [_, block] of func.body.blocks) {
103
for (const phi of block.phis) {
105
- let start = Number.MAX_SAFE_INTEGER;
106
- let end = phi.id.mutableRange.end as number;
104
for (const [_, operand] of phi.operands) {
108
- start = Math.min(start, operand.mutableRange.start);
109
- end = Math.max(end, operand.mutableRange.end);
105
+ if (
106
+ operand.mutableRange.start === 0 &&
107
+ operand.mutableRange.end === 0
108
+ ) {
109
+ // operand's range is uninitialized, skip
110
+ continue;
111
+ } else if (
112
+ phi.id.mutableRange.start === 0 &&
113
+ phi.id.mutableRange.end === 0
114
+ ) {
115
+ // phi's range is uninitialized, take the range from the operand
116
+ phi.id.mutableRange.start = operand.mutableRange.start;
117
+ phi.id.mutableRange.end = operand.mutableRange.end;
118
+ } else {
119
+ // else join the phi and operand's range
120
+ phi.id.mutableRange.start = makeInstructionId(
121
+ Math.min(phi.id.mutableRange.start, operand.mutableRange.start)
122
+ );
123
+ phi.id.mutableRange.end = makeInstructionId(
124
+ Math.max(phi.id.mutableRange.end, operand.mutableRange.end)
125
+ );
126
+ }
127
}
111
- CompilerError.invariant(start !== Number.MAX_SAFE_INTEGER, {
112
- reason: "Expected phi to have a start range value",
113
- description: null,
114
- loc: null,
115
- suggestions: null,
116
- });
117
- phi.id.mutableRange = {
118
- start: makeInstructionId(start),
119
- end: makeInstructionId(end),
120
- };
128
}
129
130
for (const instr of block.instructions) {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug-repro-missing-dependency-if-within-while.expect.md
+24
-8
@@ -39,11 +39,11 @@ export const FIXTURE_ENTRYPOINT = {
39
import { unstable_useMemoCache as useMemoCache } from "react";
40
const someGlobal = true;
41
export default function Component(props) {
42
- const $ = useMemoCache(1);
42
+ const $ = useMemoCache(4);
43
const { b } = props;
44
- let t0;
45
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
46
- const items = [];
44
+ let items;
45
+ if ($[0] !== b) {
46
+ items = [];
47
let i = 0;
48
while (i < 10) {
49
if (someGlobal) {
@@ -51,11 +51,18 @@ export default function Component(props) {
51
i++;
52
}
53
}
54
-
54
+ $[0] = b;
55
+ $[1] = items;
56
+ } else {
57
+ items = $[1];
58
+ }
59
+ let t0;
60
+ if ($[2] !== items) {
61
t0 = <>{items}</>;
56
- $[0] = t0;
62
+ $[2] = items;
63
+ $[3] = t0;
64
} else {
58
- t0 = $[0];
65
+ t0 = $[3];
66
}
67
return t0;
68
}
@@ -76,4 +83,13 @@ export const FIXTURE_ENTRYPOINT = {
83
};
84
85
```
79
-
\ No newline at end of file
86
+
87
+### Eval output
88
+(kind: ok) <div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div>
89
+<div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div>
90
+<div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div>
91
+<div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div>
92
+<div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div>
93
+<div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div>
94
+<div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div><div>0</div>
95
+<div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div><div>42</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-continue.expect.md
+36
-16
@@ -16,7 +16,17 @@ function Component(props) {
16
17
export const FIXTURE_ENTRYPOINT = {
18
fn: Component,
19
- params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
19
+ params: [{ value: { a: "a", continue: "skip", b: "hello!" } }],
20
+ sequentialRenders: [
21
+ { value: { a: "a", continue: "skip", b: "hello!" } },
22
+ { value: { a: "a", continue: "skip", b: "hello!" } },
23
+ { value: { a: "skip!", continue: true } },
24
+ { value: { a: "a", continue: "skip", b: "hello!" } },
25
+ { value: { a: "skip!", continue: true } },
26
+ { value: { a: "a", continue: "skip", b: "hello!" } },
27
+ { value: { a: "skip!", continue: true } },
28
+ { value: { a: "skip!", continue: true } },
29
+ ],
30
};
31
32
```
@@ -26,18 +36,10 @@ export const FIXTURE_ENTRYPOINT = {
36
```javascript
37
import { unstable_useMemoCache as useMemoCache } from "react";
38
function Component(props) {
29
- const $ = useMemoCache(3);
39
+ const $ = useMemoCache(2);
40
let x;
31
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32
- let t0;
33
- if ($[1] !== props.value) {
34
- t0 = { ...props.value };
35
- $[1] = props.value;
36
- $[2] = t0;
37
- } else {
38
- t0 = $[2];
39
- }
40
- const object = t0;
41
+ if ($[0] !== props.value) {
42
+ const object = { ...props.value };
43
for (const y in object) {
44
if (y === "continue") {
45
continue;
@@ -45,19 +47,37 @@ function Component(props) {
47
48
x = object[y];
49
}
48
- $[0] = x;
50
+ $[0] = props.value;
51
+ $[1] = x;
52
} else {
50
- x = $[0];
53
+ x = $[1];
54
}
55
return x;
56
}
57
58
export const FIXTURE_ENTRYPOINT = {
59
fn: Component,
57
- params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
60
+ params: [{ value: { a: "a", continue: "skip", b: "hello!" } }],
61
+ sequentialRenders: [
62
+ { value: { a: "a", continue: "skip", b: "hello!" } },
63
+ { value: { a: "a", continue: "skip", b: "hello!" } },
64
+ { value: { a: "skip!", continue: true } },
65
+ { value: { a: "a", continue: "skip", b: "hello!" } },
66
+ { value: { a: "skip!", continue: true } },
67
+ { value: { a: "a", continue: "skip", b: "hello!" } },
68
+ { value: { a: "skip!", continue: true } },
69
+ { value: { a: "skip!", continue: true } },
70
+ ],
71
};
72
73
```
74
75
### Eval output
63
-(kind: ok) "b!"
\ No newline at end of file
76
+(kind: ok) "hello!"
77
+"hello!"
78
+"skip!"
79
+"hello!"
80
+"skip!"
81
+"hello!"
82
+"skip!"
83
+"skip!"
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-continue.js
+11
-1
@@ -12,5 +12,15 @@ function Component(props) {
12
13
export const FIXTURE_ENTRYPOINT = {
14
fn: Component,
15
- params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
15
+ params: [{ value: { a: "a", continue: "skip", b: "hello!" } }],
16
+ sequentialRenders: [
17
+ { value: { a: "a", continue: "skip", b: "hello!" } },
18
+ { value: { a: "a", continue: "skip", b: "hello!" } },
19
+ { value: { a: "skip!", continue: true } },
20
+ { value: { a: "a", continue: "skip", b: "hello!" } },
21
+ { value: { a: "skip!", continue: true } },
22
+ { value: { a: "a", continue: "skip", b: "hello!" } },
23
+ { value: { a: "skip!", continue: true } },
24
+ { value: { a: "skip!", continue: true } },
25
+ ],
26
};
compiler/packages/snap/src/SproutTodoFilter.ts
-1
@@ -534,7 +534,6 @@ const skipFilter = new Set([
534
// bugs
535
"bug-reduce-reactive-deps-return-in-scope",
536
"bug-reduce-reactive-deps-break-in-scope",
537
- "bug-repro-missing-dependency-if-within-while",
537
538
// 'react-forget-runtime' not yet supported
539
"flag-enable-emit-hook-guards",