Skip to content

Commit

Permalink
compiler: merge reactive scopes across const StoreLocal (facebook#29095)
Browse files Browse the repository at this point in the history
@jbonta nerd-sniped me into making this optimization during conference
prep, posting this as a PR now that keynote is over.

Consider these two cases:

```javascript
export default function MyApp1({ count }) {
  const cb = () => count;
  return <div onclick={cb}>Hello World</div>;
}

export default function MyApp2({ count }) {
  return <div onclick={() => count}>Hello World</div>;
}
```

Previously, the former would create two reactive scopes (one for `cb`,
one for the div) while the latter would only have a single scope for the
`div` and its inline callback. The reason we created separate scopes
before is that there's a `StoreLocal 'cb' = t0` instruction in-between,
and i had conservatively implemented the merging pass to not allow
intervening StoreLocal instructions.

The observation is that intervening StoreLocals are fine _if_ the
assigned variable's last usage is the next scope. We already have a
check that the intervening lvalues are last-used at/before the next
scope, so it's trivial to extend this to support StoreLocal.

Note that we already don't merge scopes if there are intervening
terminals, so we don't have to worry about things like conditional
StoreLocal, conditional access of the resulting value, etc.
  • Loading branch information
josephsavona committed May 17, 2024
1 parent 7a149aa commit 5ab5471
Show file tree
Hide file tree
Showing 22 changed files with 304 additions and 211 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CompilerError } from "..";
import {
IdentifierId,
InstructionId,
InstructionKind,
Place,
ReactiveBlock,
ReactiveFunction,
Expand All @@ -20,6 +21,7 @@ import {
ReactiveStatement,
makeInstructionId,
} from "../HIR";
import { eachInstructionLValue } from "../HIR/visitors";
import { assertExhaustive } from "../Utils/utils";
import { printReactiveScopeSummary } from "./PrintReactiveFunction";
import {
Expand Down Expand Up @@ -186,6 +188,33 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
}
break;
}
case "StoreLocal": {
/**
* It's safe to have intervening StoreLocal instructions _if_ they are const
* and the last usage of the variable is at or before the next scope. This is
* similar to the case above for simple instructions.
*
* Reassignments are *not* safe to merge since they are a side-effect that we
* don't want to make conditional.
*/
if (current !== null) {
if (
instr.instruction.value.lvalue.kind === InstructionKind.Const
) {
for (const lvalue of eachInstructionLValue(
instr.instruction
)) {
current.lvalues.add(lvalue.identifier.id);
}
} else {
log(
`Reset scope @${current.block.scope.id} from StoreLocal in [${instr.instruction.id}]`
);
reset();
}
}
break;
}
default: {
// Other instructions are known to prevent merging, so we reset the scope if present
if (current !== null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,17 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function component() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [];
$[0] = t0;
} else {
t0 = $[0];
}
const z = t0;
const $ = _c(1);
let x;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const z = [];
const y = {};
y.z = z;
x = {};
x.y = y;
$[1] = x;
$[0] = x;
} else {
x = $[1];
x = $[0];
}
return x;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [];
Expand All @@ -33,20 +33,13 @@ function Component(props) {
const x = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = x.map((item) => item);
const y = x.map((item) => item);
t1 = [x, y];
$[1] = t1;
} else {
t1 = $[1];
}
const y = t1;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [x, y];
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
return t1;
}

export const FIXTURE_ENTRYPOINT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [];
Expand All @@ -33,20 +33,13 @@ function Component(props) {
const x = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = x.map((item) => item);
const y = x.map((item) => item);
t1 = [x, y];
$[1] = t1;
} else {
t1 = $[1];
}
const y = t1;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [x, y];
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
return t1;
}

export const FIXTURE_ENTRYPOINT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,20 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(1);
let t0;
let x;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
x = [];
t0 = x.map((item) => {
const x = [];
const y = x.map((item) => {
item.updated = true;
return item;
});
t0 = [x, y];
$[0] = t0;
$[1] = x;
} else {
t0 = $[0];
x = $[1];
}
const y = t0;
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [x, y];
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return t0;
}

export const FIXTURE_ENTRYPOINT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,20 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(1);
let t0;
let x;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
x = [];
t0 = x.map((item) => {
const x = [];
const y = x.map((item) => {
item.updated = true;
return item;
});
t0 = [x, y];
$[0] = t0;
$[1] = x;
} else {
t0 = $[0];
x = $[1];
}
const y = t0;
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [x, y];
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return t0;
}

export const FIXTURE_ENTRYPOINT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,17 @@ function foo() {
```javascript
import { c as _c } from "react/compiler-runtime";
function foo() {
const $ = _c(2);
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => <Child x={GLOBAL_IS_X} />;
const getJSX = () => <Child x={GLOBAL_IS_X} />;

t0 = getJSX();
$[0] = t0;
} else {
t0 = $[0];
}
const getJSX = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = getJSX();
$[1] = t1;
} else {
t1 = $[1];
}
const result = t1;
const result = t0;
return result;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

## Input

```javascript
import { Stringify, makeObject_Primitives } from "shared-runtime";

function Component(props) {
const array = [props.count];
const x = makeObject_Primitives();
const element = <div>{array}</div>;
console.log(x);
return <div>{element}</div>;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ count: 42 }],
};

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify, makeObject_Primitives } from "shared-runtime";

function Component(props) {
const $ = _c(6);
let t0;
if ($[0] !== props.count) {
t0 = [props.count];
$[0] = props.count;
$[1] = t0;
} else {
t0 = $[1];
}
const array = t0;
const x = makeObject_Primitives();
let t1;
if ($[2] !== array) {
t1 = <div>{array}</div>;
$[2] = array;
$[3] = t1;
} else {
t1 = $[3];
}
const element = t1;
console.log(x);
let t2;
if ($[4] !== element) {
t2 = <div>{element}</div>;
$[4] = element;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ count: 42 }],
};

```
### Eval output
(kind: ok) <div><div>42</div></div>
logs: [{ a: 0, b: 'value1', c: true }]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Stringify, makeObject_Primitives } from "shared-runtime";

function Component(props) {
const array = [props.count];
const x = makeObject_Primitives();
const element = <div>{array}</div>;
console.log(x);
return <div>{element}</div>;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ count: 42 }],
};
Loading

0 comments on commit 5ab5471

Please sign in to comment.