Update on "[compiler] Instruction reordering"

Adds a pass just after DCE to reorder safely reorderable instructions (jsx, primitives, globals) closer to where they are used, to allow other optimization passes to be more effective. Notably, the reordering allows scope merging to be more effective, since that pass relies on two scopes not having intervening instructions — in many cases we can now reorder such instructions out of the way and unlock merging, as demonstrated in the changed fixtures.

The algorithm itself is described in the docblock.

note: This is a cleaned up version of #29579 that is ready for review.

[ghstack-poisoned]
This commit is contained in:
Joe Savona
2024-06-12 15:48:34 -07:00
21 changed files with 325 additions and 277 deletions
@@ -269,7 +269,7 @@ const EnvironmentConfigSchema = z.object({
* Enable instruction reordering. See InstructionReordering.ts for the details
* of the approach.
*/
enableInstructionReordering: z.boolean().default(true),
enableInstructionReordering: z.boolean().default(false),
/*
* Enables instrumentation codegen. This emits a dev-mode only call to an
@@ -350,7 +350,7 @@ export function isStatementBlockKind(kind: BlockKind): boolean {
* Returns true for "value", "loop", and "sequence" block kinds which correspond to
* expressions in the source, such as ConditionalExpression, LogicalExpression, loop
* initializer/test/updaters, etc
*
* Inverse of isStatementBlockKind()
*/
export function isExpressionBlockKind(kind: BlockKind): boolean {
@@ -52,12 +52,11 @@ import { Stringify, identity, makeArray, mutate } from "shared-runtime";
* handles this correctly.
*/
function Foo(t0) {
const $ = _c(3);
const $ = _c(4);
const { cond1, cond2 } = t0;
const arr = makeArray({ a: 2 }, 2, []);
let t1;
if ($[0] !== cond1 || $[1] !== cond2) {
const arr = makeArray({ a: 2 }, 2, []);
if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== arr) {
t1 = cond1 ? (
<>
<div>{identity("foo")}</div>
@@ -66,9 +65,10 @@ function Foo(t0) {
) : null;
$[0] = cond1;
$[1] = cond2;
$[2] = t1;
$[2] = arr;
$[3] = t1;
} else {
t1 = $[2];
t1 = $[3];
}
return t1;
}
@@ -39,51 +39,57 @@ import { useEffect, useState } from "react";
let someGlobal = {};
function Component() {
const $ = _c(6);
const $ = _c(7);
const [state, setState] = useState(someGlobal);
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const setGlobal = () => {
t0 = () => {
someGlobal.value = true;
};
t0 = () => {
setGlobal();
};
t1 = [];
$[0] = t0;
$[1] = t1;
} else {
t0 = $[0];
t1 = $[1];
}
useEffect(t0, t1);
const setGlobal = t0;
let t1;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
setGlobal();
};
t2 = [];
$[1] = t1;
$[2] = t2;
} else {
t1 = $[1];
t2 = $[2];
}
useEffect(t1, t2);
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
let t4;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => {
setState(someGlobal.value);
};
t3 = [someGlobal];
$[2] = t2;
t4 = [someGlobal];
$[3] = t3;
} else {
t2 = $[2];
t3 = $[3];
}
useEffect(t2, t3);
const t4 = String(state);
let t5;
if ($[4] !== t4) {
t5 = <div>{t4}</div>;
$[4] = t4;
$[5] = t5;
} else {
t5 = $[5];
t3 = $[3];
t4 = $[4];
}
return t5;
useEffect(t3, t4);
const t5 = String(state);
let t6;
if ($[5] !== t5) {
t6 = <div>{t5}</div>;
$[5] = t5;
$[6] = t6;
} else {
t6 = $[6];
}
return t6;
}
export const FIXTURE_ENTRYPOINT = {
@@ -39,51 +39,57 @@ import { useEffect, useState } from "react";
let someGlobal = false;
function Component() {
const $ = _c(6);
const $ = _c(7);
const [state, setState] = useState(someGlobal);
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const setGlobal = () => {
t0 = () => {
someGlobal = true;
};
t0 = () => {
setGlobal();
};
t1 = [];
$[0] = t0;
$[1] = t1;
} else {
t0 = $[0];
t1 = $[1];
}
useEffect(t0, t1);
const setGlobal = t0;
let t1;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
setGlobal();
};
t2 = [];
$[1] = t1;
$[2] = t2;
} else {
t1 = $[1];
t2 = $[2];
}
useEffect(t1, t2);
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
let t4;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => {
setState(someGlobal);
};
t3 = [someGlobal];
$[2] = t2;
t4 = [someGlobal];
$[3] = t3;
} else {
t2 = $[2];
t3 = $[3];
}
useEffect(t2, t3);
const t4 = String(state);
let t5;
if ($[4] !== t4) {
t5 = <div>{t4}</div>;
$[4] = t4;
$[5] = t5;
} else {
t5 = $[5];
t3 = $[3];
t4 = $[4];
}
return t5;
useEffect(t3, t4);
const t5 = String(state);
let t6;
if ($[5] !== t5) {
t6 = <div>{t5}</div>;
$[5] = t5;
$[6] = t6;
} else {
t6 = $[6];
}
return t6;
}
export const FIXTURE_ENTRYPOINT = {
@@ -99,41 +99,41 @@ function Component(t0) {
t2 = $[12];
}
let t3;
if ($[13] !== a || $[14] !== b) {
t3 = [a, b];
$[13] = a;
$[14] = b;
if ($[13] !== t2 || $[14] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} />;
$[13] = t2;
$[14] = x;
$[15] = t3;
} else {
t3 = $[15];
}
let t4;
if ($[16] !== t2 || $[17] !== x) {
t4 = <ValidateMemoization inputs={t2} output={x} />;
$[16] = t2;
$[17] = x;
if ($[16] !== a || $[17] !== b) {
t4 = [a, b];
$[16] = a;
$[17] = b;
$[18] = t4;
} else {
t4 = $[18];
}
let t5;
if ($[19] !== t3 || $[20] !== z) {
t5 = <ValidateMemoization inputs={t3} output={z} />;
$[19] = t3;
if ($[19] !== t4 || $[20] !== z) {
t5 = <ValidateMemoization inputs={t4} output={z} />;
$[19] = t4;
$[20] = z;
$[21] = t5;
} else {
t5 = $[21];
}
let t6;
if ($[22] !== t4 || $[23] !== t5) {
if ($[22] !== t3 || $[23] !== t5) {
t6 = (
<>
{t4}
{t3}
{t5}
</>
);
$[22] = t4;
$[22] = t3;
$[23] = t5;
$[24] = t6;
} else {
@@ -20,7 +20,7 @@ function Foo() {
## Error
```
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 2:23(17:25)
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 2:24(18:26)
```
@@ -21,7 +21,7 @@ function Foo(props, ref) {
## Error
```
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 1:20(16:23)
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 1:21(16:23)
```
@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 3:17(4:20)
Invariant: Invalid nesting in program blocks or scopes. Items overlap but are not nested: 4:19(5:22)
```
@@ -32,7 +32,7 @@ function Component(t0) {
const $ = _c(4);
const { name, data, icon } = t0;
let t1;
if ($[0] !== name || $[1] !== data || $[2] !== icon) {
if ($[0] !== name || $[1] !== icon || $[2] !== data) {
t1 = (
<Text type="body4">
{fbt._(
@@ -62,8 +62,8 @@ function Component(t0) {
</Text>
);
$[0] = name;
$[1] = data;
$[2] = icon;
$[1] = icon;
$[2] = data;
$[3] = t1;
} else {
t1 = $[3];
@@ -42,10 +42,10 @@ import { c as _c } from "react/compiler-runtime";
import { fbt } from "fbt";
function Component() {
const $ = _c(1);
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const buttonLabel = () => {
t0 = () => {
if (!someCondition) {
return fbt._("Purchase as a gift", null, { hk: "1gHj4g" });
} else {
@@ -66,17 +66,23 @@ function Component() {
}
}
};
t0 = (
<View>
<Button text={buttonLabel()} />
</View>
);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
const buttonLabel = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
<View>
<Button text={buttonLabel()} />
</View>
);
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
```
@@ -82,41 +82,41 @@ function Component(t0) {
t4 = $[6];
}
let t5;
if ($[7] !== x || $[8] !== b) {
t5 = [x, b];
$[7] = x;
$[8] = b;
if ($[7] !== t4 || $[8] !== x) {
t5 = <ValidateMemoization inputs={t4} output={x} />;
$[7] = t4;
$[8] = x;
$[9] = t5;
} else {
t5 = $[9];
}
let t6;
if ($[10] !== t4 || $[11] !== x) {
t6 = <ValidateMemoization inputs={t4} output={x} />;
$[10] = t4;
$[11] = x;
if ($[10] !== x || $[11] !== b) {
t6 = [x, b];
$[10] = x;
$[11] = b;
$[12] = t6;
} else {
t6 = $[12];
}
let t7;
if ($[13] !== t5 || $[14] !== y) {
t7 = <ValidateMemoization inputs={t5} output={y} />;
$[13] = t5;
if ($[13] !== t6 || $[14] !== y) {
t7 = <ValidateMemoization inputs={t6} output={y} />;
$[13] = t6;
$[14] = y;
$[15] = t7;
} else {
t7 = $[15];
}
let t8;
if ($[16] !== t6 || $[17] !== t7) {
if ($[16] !== t5 || $[17] !== t7) {
t8 = (
<>
{t6}
{t5}
{t7}
</>
);
$[16] = t6;
$[16] = t5;
$[17] = t7;
$[18] = t8;
} else {
@@ -24,42 +24,69 @@ function Component(props) {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(5);
const $ = _c(15);
const item = useFragment(FRAGMENT, props.item);
useFreeze(item);
let t0;
let T0;
let t1;
let T1;
if ($[0] !== item) {
const count = new MaybeMutable(item);
T1 = View;
T0 = View;
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <span>Text</span>;
$[5] = t1;
} else {
t1 = $[5];
}
t0 = maybeMutate(count);
$[0] = item;
$[1] = t0;
$[2] = T0;
$[3] = t1;
$[4] = T1;
} else {
t0 = $[1];
}
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <span>Text</span>;
$[2] = t1;
} else {
t1 = $[2];
T0 = $[2];
t1 = $[3];
T1 = $[4];
}
let t2;
if ($[3] !== t0) {
t2 = (
<View>
<View>
{t1}
<span>{t0}</span>
</View>
</View>
);
$[3] = t0;
$[4] = t2;
if ($[6] !== t0) {
t2 = <span>{t0}</span>;
$[6] = t0;
$[7] = t2;
} else {
t2 = $[4];
t2 = $[7];
}
return t2;
let t3;
if ($[8] !== T0 || $[9] !== t1 || $[10] !== t2) {
t3 = (
<T0>
{t1}
{t2}
</T0>
);
$[8] = T0;
$[9] = t1;
$[10] = t2;
$[11] = t3;
} else {
t3 = $[11];
}
let t4;
if ($[12] !== T1 || $[13] !== t3) {
t4 = <T1>{t3}</T1>;
$[12] = T1;
$[13] = t3;
$[14] = t4;
} else {
t4 = $[14];
}
return t4;
}
```
@@ -36,29 +36,39 @@ import { c as _c } from "react/compiler-runtime";
import { StaticText1 } from "shared-runtime";
function Component() {
const $ = _c(1);
const $ = _c(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (
t0 = <StaticText1 />;
$[0] = t0;
} else {
t0 = $[0];
}
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <StaticText1 />;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = (
<div>
Before text
<StaticText1 />
Middle text
Before text{t0}Middle text
<StaticText1>
Inner before text
<StaticText1 />
Inner middle text
Inner before text{t1}Inner middle text
<StaticText1 />
Inner after text
</StaticText1>
After text
</div>
);
$[0] = t0;
$[2] = t2;
} else {
t0 = $[0];
t2 = $[2];
}
return t0;
return t2;
}
export const FIXTURE_ENTRYPOINT = {
@@ -44,7 +44,7 @@ import { Stringify } from "shared-runtime";
// prevent scome scopes from merging, which concealed a bug with the merging logic.
// By avoiding JSX we eliminate extraneous instructions and more accurately test the merging.
function Component(props) {
const $ = _c(10);
const $ = _c(11);
const [state, setState] = useState(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -54,44 +54,47 @@ function Component(props) {
t0 = $[0];
}
let t1;
let t2;
if ($[1] !== state) {
t1 = { component: "span", props: { children: [state] } };
t2 = () => setState(state + 1);
$[1] = state;
$[2] = t1;
$[3] = t2;
} else {
t1 = $[2];
t2 = $[3];
}
let t2;
if ($[3] !== state) {
t2 = () => setState(state + 1);
$[3] = state;
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
t3 = ["increment"];
$[4] = t3;
$[5] = t3;
} else {
t3 = $[4];
t3 = $[5];
}
let t4;
if ($[5] !== t2) {
if ($[6] !== t2) {
t4 = {
component: "button",
props: { "data-testid": "button", onClick: t2, children: t3 },
};
$[5] = t2;
$[6] = t4;
$[6] = t2;
$[7] = t4;
} else {
t4 = $[6];
t4 = $[7];
}
let t5;
if ($[7] !== t1 || $[8] !== t4) {
if ($[8] !== t1 || $[9] !== t4) {
t5 = [t0, t1, t4];
$[7] = t1;
$[8] = t4;
$[9] = t5;
$[8] = t1;
$[9] = t4;
$[10] = t5;
} else {
t5 = $[9];
t5 = $[10];
}
return t5;
}
@@ -33,59 +33,51 @@ import { useState } from "react";
import { Stringify } from "shared-runtime";
function Component() {
const $ = _c(10);
const $ = _c(8);
const [state, setState] = useState(0);
let t0;
if ($[0] !== state) {
t0 = () => setState(state + 1);
$[0] = state;
$[1] = t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Stringify text="Counter" />;
$[0] = t0;
} else {
t0 = $[1];
t0 = $[0];
}
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Stringify text="Counter" />;
if ($[1] !== state) {
t1 = <span>{state}</span>;
$[1] = state;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[3] !== state) {
t2 = <span>{state}</span>;
t2 = (
<button data-testid="button" onClick={() => setState(state + 1)}>
increment
</button>
);
$[3] = state;
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[5] !== t0) {
if ($[5] !== t1 || $[6] !== t2) {
t3 = (
<button data-testid="button" onClick={t0}>
increment
</button>
);
$[5] = t0;
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] !== t2 || $[8] !== t3) {
t4 = (
<div>
{t0}
{t1}
{t2}
{t3}
</div>
);
$[7] = t2;
$[8] = t3;
$[9] = t4;
$[5] = t1;
$[6] = t2;
$[7] = t3;
} else {
t4 = $[9];
t3 = $[7];
}
return t4;
return t3;
}
export const FIXTURE_ENTRYPOINT = {
@@ -52,12 +52,11 @@ import { Stringify, identity, makeArray, mutate } from "shared-runtime";
* handles this correctly.
*/
function Foo(t0) {
const $ = _c(3);
const $ = _c(4);
const { cond1, cond2 } = t0;
const arr = makeArray({ a: 2 }, 2, []);
let t1;
if ($[0] !== cond1 || $[1] !== cond2) {
const arr = makeArray({ a: 2 }, 2, []);
if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== arr) {
t1 = cond1 ? (
<>
<div>{identity("foo")}</div>
@@ -66,9 +65,10 @@ function Foo(t0) {
) : null;
$[0] = cond1;
$[1] = cond2;
$[2] = t1;
$[2] = arr;
$[3] = t1;
} else {
t1 = $[2];
t1 = $[3];
}
return t1;
}
@@ -29,39 +29,30 @@ import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
function Component(t0) {
const $ = _c(5);
const $ = _c(3);
const { id } = t0;
const t1 = id ? true : false;
let t2;
if ($[0] !== t1) {
t2 = <Stringify title={t1} />;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Stringify title={undefined} />;
$[0] = t1;
$[1] = t2;
} else {
t2 = $[1];
t1 = $[0];
}
const t2 = id ? true : false;
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <Stringify title={undefined} />;
if ($[1] !== t2) {
t3 = (
<>
{t1}
<Stringify title={t2} />
</>
);
$[1] = t2;
$[2] = t3;
} else {
t3 = $[2];
}
let t4;
if ($[3] !== t2) {
t4 = (
<>
{t3}
{t2}
</>
);
$[3] = t2;
$[4] = t4;
} else {
t4 = $[4];
}
return t4;
return t3;
}
export const FIXTURE_ENTRYPOINT = {
@@ -35,9 +35,10 @@ import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRul
import { Stringify, identity, useHook } from "shared-runtime";
function Component(t0) {
const $ = _c(15);
const $ = _c(17);
const { index } = t0;
const data = useHook();
let T0;
let t1;
let t2;
let t3;
@@ -46,60 +47,62 @@ function Component(t0) {
const b = identity(data, index);
const c = identity(data, index);
t1 = identity(b);
t2 = identity(a);
t3 = identity(c);
const t4 = identity(b);
if ($[6] !== t4) {
t2 = <Stringify value={t4} />;
$[6] = t4;
$[7] = t2;
} else {
t2 = $[7];
}
const t5 = identity(a);
if ($[8] !== t5) {
t3 = <Stringify value={t5} />;
$[8] = t5;
$[9] = t3;
} else {
t3 = $[9];
}
T0 = Stringify;
t1 = identity(c);
$[0] = data;
$[1] = index;
$[2] = t1;
$[3] = t2;
$[4] = t3;
$[2] = T0;
$[3] = t1;
$[4] = t2;
$[5] = t3;
} else {
t1 = $[2];
t2 = $[3];
t3 = $[4];
T0 = $[2];
t1 = $[3];
t2 = $[4];
t3 = $[5];
}
let t4;
if ($[5] !== t1) {
t4 = <Stringify value={t1} />;
$[5] = t1;
$[6] = t4;
if ($[10] !== T0 || $[11] !== t1) {
t4 = <T0 value={t1} />;
$[10] = T0;
$[11] = t1;
$[12] = t4;
} else {
t4 = $[6];
t4 = $[12];
}
let t5;
if ($[7] !== t2) {
t5 = <Stringify value={t2} />;
$[7] = t2;
$[8] = t5;
} else {
t5 = $[8];
}
let t6;
if ($[9] !== t3) {
t6 = <Stringify value={t3} />;
$[9] = t3;
$[10] = t6;
} else {
t6 = $[10];
}
let t7;
if ($[11] !== t4 || $[12] !== t5 || $[13] !== t6) {
t7 = (
if ($[13] !== t2 || $[14] !== t3 || $[15] !== t4) {
t5 = (
<div>
{t2}
{t3}
{t4}
{t5}
{t6}
</div>
);
$[11] = t4;
$[12] = t5;
$[13] = t6;
$[14] = t7;
$[13] = t2;
$[14] = t3;
$[15] = t4;
$[16] = t5;
} else {
t7 = $[14];
t5 = $[16];
}
return t7;
return t5;
}
export const FIXTURE_ENTRYPOINT = {
@@ -17,30 +17,34 @@ function Component(listItem, thread) {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(listItem, thread) {
const $ = _c(6);
const $ = _c(7);
let t0;
let t1;
let t2;
if ($[0] !== thread.threadType || $[1] !== listItem) {
const isFoo = isFooThread(thread.threadType);
t1 = listItem;
t1 = useBar;
t2 = listItem;
t0 = getBadgeText(listItem, isFoo);
$[0] = thread.threadType;
$[1] = listItem;
$[2] = t0;
$[3] = t1;
$[4] = t2;
} else {
t0 = $[2];
t1 = $[3];
t2 = $[4];
}
let t2;
if ($[4] !== t0) {
t2 = [t0];
$[4] = t0;
$[5] = t2;
let t3;
if ($[5] !== t0) {
t3 = [t0];
$[5] = t0;
$[6] = t3;
} else {
t2 = $[5];
t3 = $[6];
}
const body = useBar(t1, t2);
const body = t1(t2, t3);
return body;
}
@@ -56,33 +56,33 @@ function Component(t0) {
t2 = $[1];
}
let t3;
if ($[2] !== cond) {
t3 = cond === false && (
<div className={identity(styles.c, DISPLAY ? styles.d : {})} />
);
$[2] = cond;
if ($[2] !== t2) {
t3 = <div className={t2} />;
$[2] = t2;
$[3] = t3;
} else {
t3 = $[3];
}
let t4;
if ($[4] !== t2) {
t4 = <div className={t2} />;
$[4] = t2;
if ($[4] !== cond) {
t4 = cond === false && (
<div className={identity(styles.c, DISPLAY ? styles.d : {})} />
);
$[4] = cond;
$[5] = t4;
} else {
t4 = $[5];
}
let t5;
if ($[6] !== t4 || $[7] !== t3) {
if ($[6] !== t3 || $[7] !== t4) {
t5 = (
<>
{t4}
{t3}
{t4}
</>
);
$[6] = t4;
$[7] = t3;
$[6] = t3;
$[7] = t4;
$[8] = t5;
} else {
t5 = $[8];