Skip to content

Commit 546b4bf

Browse files
committed
[compiler][fixtures] test repros: codegen, alignScope, phis
ghstack-source-id: d30fd3f Pull Request resolved: #29878 The AlignReactiveScope bug should be simplest to fix, but it's also caught by an invariant assertion. I think a fix could be either keeping track of "active" block-fallthrough pairs (`retainWhere(pair => pair.range.end > current.instr[0].id)`) or following the approach in `assertValidBlockNesting`. I'm tempted to pull the value-block aligning logic out into its own pass (using the current `node` tree traversal), then align to non-value blocks with the `assertValidBlockNesting` approach. Happy to hear feedback on this though! The other two are likely bigger issues, as they're not caught by static invariants. Update: - removed bug-phi-reference-effect as it's been patched by @josephsavona - added bug-array-concat-should-capture
1 parent dfd3097 commit 546b4bf

10 files changed

+412
-19
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
2+
## Input
3+
4+
```javascript
5+
import { mutate } from "shared-runtime";
6+
7+
/**
8+
* Fixture showing why `concat` needs to capture both the callee and rest args.
9+
* Here, observe that arr1's values are captured into arr2.
10+
* - Later mutations of arr2 may write to values within arr1.
11+
* - Observe that it's technically valid to separately memoize the array arr1
12+
* itself.
13+
*/
14+
function Foo({ inputNum }) {
15+
const arr1: Array<number | object> = [{ a: 1 }, {}];
16+
const arr2 = arr1.concat([1, inputNum]);
17+
mutate(arr2[0]);
18+
return arr2;
19+
}
20+
21+
export const FIXTURE_ENTRYPOINT = {
22+
fn: Foo,
23+
params: [{ inputNum: 2 }],
24+
sequentialRenders: [{ inputNum: 2 }, { inputNum: 3 }],
25+
};
26+
27+
```
28+
29+
## Code
30+
31+
```javascript
32+
import { c as _c } from "react/compiler-runtime";
33+
import { mutate } from "shared-runtime";
34+
35+
/**
36+
* Fixture showing why `concat` needs to capture both the callee and rest args.
37+
* Here, observe that arr1's values are captured into arr2.
38+
* - Later mutations of arr2 may write to values within arr1.
39+
* - Observe that it's technically valid to separately memoize the array arr1
40+
* itself.
41+
*/
42+
function Foo(t0) {
43+
const $ = _c(3);
44+
const { inputNum } = t0;
45+
let t1;
46+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
47+
t1 = [{ a: 1 }, {}];
48+
$[0] = t1;
49+
} else {
50+
t1 = $[0];
51+
}
52+
const arr1 = t1;
53+
let arr2;
54+
if ($[1] !== inputNum) {
55+
arr2 = arr1.concat([1, inputNum]);
56+
mutate(arr2[0]);
57+
$[1] = inputNum;
58+
$[2] = arr2;
59+
} else {
60+
arr2 = $[2];
61+
}
62+
return arr2;
63+
}
64+
65+
export const FIXTURE_ENTRYPOINT = {
66+
fn: Foo,
67+
params: [{ inputNum: 2 }],
68+
sequentialRenders: [{ inputNum: 2 }, { inputNum: 3 }],
69+
};
70+
71+
```
72+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { mutate } from "shared-runtime";
2+
3+
/**
4+
* Fixture showing why `concat` needs to capture both the callee and rest args.
5+
* Here, observe that arr1's values are captured into arr2.
6+
* - Later mutations of arr2 may write to values within arr1.
7+
* - Observe that it's technically valid to separately memoize the array arr1
8+
* itself.
9+
*/
10+
function Foo({ inputNum }) {
11+
const arr1: Array<number | object> = [{ a: 1 }, {}];
12+
const arr2 = arr1.concat([1, inputNum]);
13+
mutate(arr2[0]);
14+
return arr2;
15+
}
16+
17+
export const FIXTURE_ENTRYPOINT = {
18+
fn: Foo,
19+
params: [{ inputNum: 2 }],
20+
sequentialRenders: [{ inputNum: 2 }, { inputNum: 3 }],
21+
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
2+
## Input
3+
4+
```javascript
5+
import { makeArray, print } from "shared-runtime";
6+
7+
/**
8+
* Exposes bug involving iife inlining + codegen.
9+
* We currently inline iifes to labeled blocks (not value-blocks).
10+
*
11+
* Here, print(1) and the evaluation of makeArray(...) get the same scope
12+
* as the compiler infers that the makeArray call may mutate its arguments.
13+
* Since print(1) does not get its own scope (and is thus not a declaration
14+
* or dependency), it does not get promoted.
15+
* As a result, print(1) gets reordered across the labeled-block instructions
16+
* to be inlined at the makeArray callsite.
17+
*
18+
* Current evaluator results:
19+
* Found differences in evaluator results
20+
* Non-forget (expected):
21+
* (kind: ok) [null,2]
22+
* logs: [1,2]
23+
* Forget:
24+
* (kind: ok) [null,2]
25+
* logs: [2,1]
26+
*/
27+
function useTest() {
28+
return makeArray<number | void>(
29+
print(1),
30+
(function foo() {
31+
print(2);
32+
return 2;
33+
})()
34+
);
35+
}
36+
37+
export const FIXTURE_ENTRYPOINT = {
38+
fn: useTest,
39+
params: [],
40+
};
41+
42+
```
43+
44+
## Code
45+
46+
```javascript
47+
import { c as _c } from "react/compiler-runtime";
48+
import { makeArray, print } from "shared-runtime";
49+
50+
/**
51+
* Exposes bug involving iife inlining + codegen.
52+
* We currently inline iifes to labeled blocks (not value-blocks).
53+
*
54+
* Here, print(1) and the evaluation of makeArray(...) get the same scope
55+
* as the compiler infers that the makeArray call may mutate its arguments.
56+
* Since print(1) does not get its own scope (and is thus not a declaration
57+
* or dependency), it does not get promoted.
58+
* As a result, print(1) gets reordered across the labeled-block instructions
59+
* to be inlined at the makeArray callsite.
60+
*
61+
* Current evaluator results:
62+
* Found differences in evaluator results
63+
* Non-forget (expected):
64+
* (kind: ok) [null,2]
65+
* logs: [1,2]
66+
* Forget:
67+
* (kind: ok) [null,2]
68+
* logs: [2,1]
69+
*/
70+
function useTest() {
71+
const $ = _c(1);
72+
let t0;
73+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
74+
let t1;
75+
76+
print(2);
77+
t1 = 2;
78+
t0 = makeArray(print(1), t1);
79+
$[0] = t0;
80+
} else {
81+
t0 = $[0];
82+
}
83+
return t0;
84+
}
85+
86+
export const FIXTURE_ENTRYPOINT = {
87+
fn: useTest,
88+
params: [],
89+
};
90+
91+
```
92+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { makeArray, print } from "shared-runtime";
2+
3+
/**
4+
* Exposes bug involving iife inlining + codegen.
5+
* We currently inline iifes to labeled blocks (not value-blocks).
6+
*
7+
* Here, print(1) and the evaluation of makeArray(...) get the same scope
8+
* as the compiler infers that the makeArray call may mutate its arguments.
9+
* Since print(1) does not get its own scope (and is thus not a declaration
10+
* or dependency), it does not get promoted.
11+
* As a result, print(1) gets reordered across the labeled-block instructions
12+
* to be inlined at the makeArray callsite.
13+
*
14+
* Current evaluator results:
15+
* Found differences in evaluator results
16+
* Non-forget (expected):
17+
* (kind: ok) [null,2]
18+
* logs: [1,2]
19+
* Forget:
20+
* (kind: ok) [null,2]
21+
* logs: [2,1]
22+
*/
23+
function useTest() {
24+
return makeArray<number | void>(
25+
print(1),
26+
(function foo() {
27+
print(2);
28+
return 2;
29+
})()
30+
);
31+
}
32+
33+
export const FIXTURE_ENTRYPOINT = {
34+
fn: useTest,
35+
params: [],
36+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
## Input
3+
4+
```javascript
5+
/**
6+
* Similar fixture to `error.todo-align-scopes-nested-block-structure`, but
7+
* a simpler case.
8+
*/
9+
function useFoo(cond) {
10+
let s = null;
11+
if (cond) {
12+
s = {};
13+
} else {
14+
return null;
15+
}
16+
mutate(s);
17+
return s;
18+
}
19+
20+
```
21+
22+
23+
## Error
24+
25+
```
26+
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 4:10(5:13)
27+
```
28+
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Similar fixture to `error.todo-align-scopes-nested-block-structure`, but
3+
* a simpler case.
4+
*/
5+
function useFoo(cond) {
6+
let s = null;
7+
if (cond) {
8+
s = {};
9+
} else {
10+
return null;
11+
}
12+
mutate(s);
13+
return s;
14+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
2+
## Input
3+
4+
```javascript
5+
/**
6+
* Fixture showing that it's not sufficient to only align direct scoped
7+
* accesses of a block-fallthrough pair.
8+
* Below is a simplified view of HIR blocks in this fixture.
9+
* Note that here, s is mutated in both bb1 and bb4. However, neither
10+
* bb1 nor bb4 have terminal fallthroughs or are fallthroughs themselves.
11+
*
12+
* This means that we need to recursively visit all scopes accessed between
13+
* a block and its fallthrough and extend the range of those scopes which overlap
14+
* with an active block/fallthrough pair,
15+
*
16+
* bb0
17+
* ┌──────────────┐
18+
* │let s = null │
19+
* │test cond1 │
20+
* │ <fallthr=bb3>│
21+
* └┬─────────────┘
22+
* │ bb1
23+
* ├─►┌───────┐
24+
* │ │s = {} ├────┐
25+
* │ └───────┘ │
26+
* │ bb2 │
27+
* └─►┌───────┐ │
28+
* │return;│ │
29+
* └───────┘ │
30+
* bb3 │
31+
* ┌──────────────┐◄┘
32+
* │test cond2 │
33+
* │ <fallthr=bb5>│
34+
* └┬─────────────┘
35+
* │ bb4
36+
* ├─►┌─────────┐
37+
* │ │mutate(s)├─┐
38+
* ▼ └─────────┘ │
39+
* bb5 │
40+
* ┌───────────┐ │
41+
* │return s; │◄──┘
42+
* └───────────┘
43+
*/
44+
function useFoo(cond1, cond2) {
45+
let s = null;
46+
if (cond1) {
47+
s = {};
48+
} else {
49+
return null;
50+
}
51+
52+
if (cond2) {
53+
mutate(s);
54+
}
55+
56+
return s;
57+
}
58+
59+
```
60+
61+
62+
## Error
63+
64+
```
65+
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 4:10(5:15)
66+
```
67+
68+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Fixture showing that it's not sufficient to only align direct scoped
3+
* accesses of a block-fallthrough pair.
4+
* Below is a simplified view of HIR blocks in this fixture.
5+
* Note that here, s is mutated in both bb1 and bb4. However, neither
6+
* bb1 nor bb4 have terminal fallthroughs or are fallthroughs themselves.
7+
*
8+
* This means that we need to recursively visit all scopes accessed between
9+
* a block and its fallthrough and extend the range of those scopes which overlap
10+
* with an active block/fallthrough pair,
11+
*
12+
* bb0
13+
* ┌──────────────┐
14+
* │let s = null │
15+
* │test cond1 │
16+
* │ <fallthr=bb3>│
17+
* └┬─────────────┘
18+
* │ bb1
19+
* ├─►┌───────┐
20+
* │ │s = {} ├────┐
21+
* │ └───────┘ │
22+
* │ bb2 │
23+
* └─►┌───────┐ │
24+
* │return;│ │
25+
* └───────┘ │
26+
* bb3 │
27+
* ┌──────────────┐◄┘
28+
* │test cond2 │
29+
* │ <fallthr=bb5>│
30+
* └┬─────────────┘
31+
* │ bb4
32+
* ├─►┌─────────┐
33+
* │ │mutate(s)├─┐
34+
* ▼ └─────────┘ │
35+
* bb5 │
36+
* ┌───────────┐ │
37+
* │return s; │◄──┘
38+
* └───────────┘
39+
*/
40+
function useFoo(cond1, cond2) {
41+
let s = null;
42+
if (cond1) {
43+
s = {};
44+
} else {
45+
return null;
46+
}
47+
48+
if (cond2) {
49+
mutate(s);
50+
}
51+
52+
return s;
53+
}

0 commit comments

Comments
 (0)