@samitouri / QOS-React-1 / commits / fbd68eef7f

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",