Reorder InlineUseMemo after type inference

The goal of this stack is to generalize `InlineUseMemo` into a pass that inlines 
all immediately invoked function expressions (IIFEs). Rather than specialize 
just useMemo calls, we'll rely on DropManualMemoization running first and 
turning useMemo calls into IIFEs. Then the generalized inlining pass can handle 
those IIFEs as well as others present in the source. 

For now, moving the order of the pass makes the output closer to what it will 
eventually be after this stack is complete.
This commit is contained in:
Joe Savona
2023-10-09 15:25:18 -07:00
parent 74c6c992ba
commit 444a1ad9c1
20 changed files with 184 additions and 176 deletions
@@ -113,9 +113,6 @@ function* runWithEnvironment(
pruneMaybeThrows(hir);
yield log({ kind: "hir", name: "PruneMaybeThrows", value: hir });
inlineUseMemo(hir);
yield log({ kind: "hir", name: "RewriteUseMemo", value: hir });
mergeConsecutiveBlocks(hir);
yield log({ kind: "hir", name: "MergeConsecutiveBlocks", value: hir });
@@ -146,6 +143,9 @@ function* runWithEnvironment(
});
}
inlineUseMemo(hir);
yield log({ kind: "hir", name: "InlineUseMemo", value: hir });
dropManualMemoization(hir);
yield log({ kind: "hir", name: "DropManualMemoization", value: hir });
@@ -20,6 +20,7 @@ import {
InstructionKind,
LabelTerminal,
Place,
getHookKind,
makeInstructionId,
makeType,
reversePostorderBlocks,
@@ -61,8 +62,6 @@ import { retainWhere } from "../Utils/utils";
export function inlineUseMemo(fn: HIRFunction): void {
// Track all function expressions in case they appear as the argument to a useMemo
const functions = new Map<IdentifierId, FunctionExpression>();
// Track all references to `useMemo`
const useMemoGlobals = new Set<IdentifierId>();
// Identifiers (lvalues) for known useMemo functions, so that we can prune them
// at the end of the pass
const useMemoFunctions = new Set<IdentifierId>();
@@ -77,103 +76,100 @@ export function inlineUseMemo(fn: HIRFunction): void {
for (let ii = 0; ii < block.instructions.length; ii++) {
const instr = block.instructions[ii]!;
switch (instr.value.kind) {
case "LoadGlobal": {
if (instr.value.name === "useMemo") {
useMemoGlobals.add(instr.lvalue.identifier.id);
}
break;
}
case "FunctionExpression": {
functions.set(instr.lvalue.identifier.id, instr.value);
break;
}
case "MethodCall":
case "CallExpression": {
if (useMemoGlobals.has(instr.value.callee.identifier.id)) {
const [lambda] = instr.value.args;
if (lambda.kind === "Spread") {
continue;
}
const body = functions.get(lambda.identifier.id);
if (body === undefined) {
// Allow passing a named function to useMemo, eg `useMemo(someImportedFunction, [])`
continue;
}
if (body.loweredFunc.func.params.length > 0) {
CompilerError.invalidReact({
reason: "useMemo callbacks may not accept any arguments",
description: null,
loc: body.loc,
suggestions: null,
});
}
if (
body.loweredFunc.func.async ||
body.loweredFunc.func.generator
) {
CompilerError.invalidReact({
reason:
"useMemo callbacks may not be async or generator functions",
description: null,
loc: body.loc,
suggestions: null,
});
}
// We know this function is used for useMemo and can prune it later
useMemoFunctions.add(lambda.identifier.id);
// Create a new block which will contain code following the useMemo call
const continuationBlockId = fn.env.nextBlockId;
const continuationBlock: BasicBlock = {
id: continuationBlockId,
instructions: block.instructions.slice(ii + 1),
kind: block.kind,
phis: new Set(),
preds: new Set(),
terminal: block.terminal,
};
fn.body.blocks.set(continuationBlockId, continuationBlock);
// Trim the original block to contain instructions up to (but not including)
// the useMemo
block.instructions.length = ii;
// To account for complex control flow within the lambda, we treat the lambda
// as if it were a single labeled statement, and replace all returns with gotos
// to the label fallthrough.
const newTerminal: LabelTerminal = {
block: body.loweredFunc.func.body.entry,
id: makeInstructionId(0),
kind: "label",
fallthrough: continuationBlockId,
loc: block.terminal.loc,
};
block.terminal = newTerminal;
// We store the result in the useMemo temporary
const result = instr.lvalue;
// Declare the useMemo temporary
declareTemporary(fn.env, block, result);
// Promote the temporary with a name as we require this to persist
promoteTemporary(result.identifier);
// Rewrite blocks from the lambda to replace any `return` with a
// store to the result and `goto` the continuation block
for (const [id, block] of body.loweredFunc.func.body.blocks) {
block.preds.clear();
rewriteBlock(fn.env, block, continuationBlockId, result);
fn.body.blocks.set(id, block);
}
// Ensure we visit the continuation block, since there may have been
// sequential useMemos that need to be visited.
queue.push(continuationBlock);
continue queue;
const hookKind =
instr.value.kind === "CallExpression"
? getHookKind(fn.env, instr.value.callee.identifier)
: getHookKind(fn.env, instr.value.property.identifier);
if (hookKind !== "useMemo") {
continue;
}
const [lambda] = instr.value.args;
if (lambda.kind === "Spread") {
continue;
}
const body = functions.get(lambda.identifier.id);
if (body === undefined) {
// Allow passing a named function to useMemo, eg `useMemo(someImportedFunction, [])`
continue;
}
if (body.loweredFunc.func.params.length > 0) {
CompilerError.invalidReact({
reason: "useMemo callbacks may not accept any arguments",
description: null,
loc: body.loc,
suggestions: null,
});
}
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
CompilerError.invalidReact({
reason:
"useMemo callbacks may not be async or generator functions",
description: null,
loc: body.loc,
suggestions: null,
});
}
// We know this function is used for useMemo and can prune it later
useMemoFunctions.add(lambda.identifier.id);
// Create a new block which will contain code following the useMemo call
const continuationBlockId = fn.env.nextBlockId;
const continuationBlock: BasicBlock = {
id: continuationBlockId,
instructions: block.instructions.slice(ii + 1),
kind: block.kind,
phis: new Set(),
preds: new Set(),
terminal: block.terminal,
};
fn.body.blocks.set(continuationBlockId, continuationBlock);
// Trim the original block to contain instructions up to (but not including)
// the useMemo
block.instructions.length = ii;
// To account for complex control flow within the lambda, we treat the lambda
// as if it were a single labeled statement, and replace all returns with gotos
// to the label fallthrough.
const newTerminal: LabelTerminal = {
block: body.loweredFunc.func.body.entry,
id: makeInstructionId(0),
kind: "label",
fallthrough: continuationBlockId,
loc: block.terminal.loc,
};
block.terminal = newTerminal;
// We store the result in the useMemo temporary
const result = instr.lvalue;
// Declare the useMemo temporary
declareTemporary(fn.env, block, result);
// Promote the temporary with a name as we require this to persist
promoteTemporary(result.identifier);
// Rewrite blocks from the lambda to replace any `return` with a
// store to the result and `goto` the continuation block
for (const [id, block] of body.loweredFunc.func.body.blocks) {
block.preds.clear();
rewriteBlock(fn.env, block, continuationBlockId, result);
fn.body.blocks.set(id, block);
}
// Ensure we visit the continuation block, since there may have been
// sequential useMemos that need to be visited.
queue.push(continuationBlock);
continue queue;
}
}
}
@@ -32,6 +32,7 @@ import {
function Component(props) {
const $ = useMemoCache(4);
const [x] = useState(0);
let t35;
const c_0 = $[0] !== x;
let t0;
if (c_0) {
@@ -41,8 +42,8 @@ function Component(props) {
} else {
t0 = $[1];
}
const t15 = t0;
const expensiveNumber = t15;
t35 = t0;
const expensiveNumber = t35;
const c_2 = $[2] !== expensiveNumber;
let t1;
if (c_2) {
@@ -58,6 +59,7 @@ function Component(props) {
function Component2(props) {
const $ = useMemoCache(4);
const [x] = useState(0);
let t35;
const c_0 = $[0] !== x;
let t0;
if (c_0) {
@@ -67,8 +69,8 @@ function Component2(props) {
} else {
t0 = $[1];
}
const t15 = t0;
const expensiveNumber = t15;
t35 = t0;
const expensiveNumber = t35;
const c_2 = $[2] !== expensiveNumber;
let t1;
if (c_2) {
@@ -34,6 +34,7 @@ import {
function Component(props) {
const $ = useMemoCache(4);
const [x] = useState(0);
let t35;
const c_0 = $[0] !== x;
let t0;
if (c_0) {
@@ -43,8 +44,8 @@ function Component(props) {
} else {
t0 = $[1];
}
const t15 = t0;
const expensiveNumber = t15;
t35 = t0;
const expensiveNumber = t35;
const c_2 = $[2] !== expensiveNumber;
let t1;
if (c_2) {
@@ -60,6 +61,7 @@ function Component(props) {
function Component2(props) {
const $ = useMemoCache(4);
const [x] = useState(0);
let t35;
const c_0 = $[0] !== x;
let t0;
if (c_0) {
@@ -69,8 +71,8 @@ function Component2(props) {
} else {
t0 = $[1];
}
const t15 = t0;
const expensiveNumber = t15;
t35 = t0;
const expensiveNumber = t35;
const c_2 = $[2] !== expensiveNumber;
let t1;
if (c_2) {
@@ -29,7 +29,9 @@ import { calculateExpensiveNumber } from "shared-runtime";
function Component(props) {
const $ = useMemoCache(2);
const [x] = React.useState(0);
const expensiveNumber = (() => calculateExpensiveNumber(x))();
let t39;
t39 = calculateExpensiveNumber(x);
const expensiveNumber = t39;
const c_0 = $[0] !== expensiveNumber;
let t0;
if (c_0) {
@@ -24,11 +24,12 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function App(t25) {
function App(t23) {
const $ = useMemoCache(2);
const { text, hasDeps } = t25;
const { text, hasDeps } = t23;
hasDeps ? null : [text];
let t44;
const c_0 = $[0] !== text;
let t0;
if (c_0) {
@@ -38,8 +39,8 @@ function App(t25) {
} else {
t0 = $[1];
}
const t18 = t0;
const resolvedText = t18;
t44 = t0;
const resolvedText = t44;
return resolvedText;
}
@@ -27,30 +27,20 @@ import { unstable_useMemoCache as useMemoCache } from "react";
import * as React from "react";
function Component(props) {
const $ = useMemoCache(4);
const $ = useMemoCache(2);
let t42;
const c_0 = $[0] !== props.value;
let t0;
let x;
if (c_0) {
t0 = () => {
const x = [];
x.push(props.value);
return x;
};
x = [];
x.push(props.value);
$[0] = props.value;
$[1] = t0;
$[1] = x;
} else {
t0 = $[1];
x = $[1];
}
const c_2 = $[2] !== t0;
let t1;
if (c_2) {
t1 = t0();
$[2] = t0;
$[3] = t1;
} else {
t1 = $[3];
}
const x_0 = t1;
t42 = x;
const x_0 = t42;
return x_0;
}
@@ -28,10 +28,11 @@ function Component(props) {
};
const object = { x, onChange };
let t86;
const { x: x_0, onChange: onChange_0 } = object;
const t43 = <input value={x_0} onChange={onChange_0} />;
return t43;
t86 = <input value={x_0} onChange={onChange_0} />;
return t86;
}
```
@@ -20,8 +20,8 @@ function Component(props) {
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(4);
let t20 = undefined;
bb7: {
let t44;
bb8: {
if (props.cond) {
const c_0 = $[0] !== props.a;
let t0;
@@ -32,8 +32,8 @@ function Component(props) {
} else {
t0 = $[1];
}
t20 = t0;
break bb7;
t44 = t0;
break bb8;
}
const c_2 = $[2] !== props.b;
let t1;
@@ -44,9 +44,9 @@ function Component(props) {
} else {
t1 = $[3];
}
t20 = t1;
t44 = t1;
}
const x = t20;
const x = t44;
return x;
}
@@ -20,6 +20,7 @@ function Component(props) {
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(10);
let t59;
const c_0 = $[0] !== props.a;
let t0;
if (c_0) {
@@ -51,8 +52,8 @@ function Component(props) {
} else {
t2 = $[6];
}
const t26 = t2;
const [a_0, b_0] = t26;
t59 = t2;
const [a_0, b_0] = t59;
const c_7 = $[7] !== a_0;
const c_8 = $[8] !== b_0;
let t3;
@@ -25,8 +25,8 @@ export const FIXTURE_ENTRYPOINT = {
import { unstable_useMemoCache as useMemoCache } from "react";
function component(a, b) {
const $ = useMemoCache(2);
let t13 = undefined;
bb6: {
let t31;
bb7: {
if (a) {
const c_0 = $[0] !== b;
let t0;
@@ -37,12 +37,12 @@ function component(a, b) {
} else {
t0 = $[1];
}
t13 = t0;
break bb6;
t31 = t0;
break bb7;
}
t13 = undefined;
t31 = undefined;
}
const x = t13;
const x = t31;
return x;
}
@@ -27,20 +27,20 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function Component(props) {
let t16 = undefined;
bb10: {
let t36;
bb11: {
bb5: {
if (props.cond) {
break bb5;
}
t16 = props.a;
break bb10;
t36 = props.a;
break bb11;
}
t16 = props.b;
t36 = props.b;
}
const x = t16;
const x = t36;
return x;
}
@@ -23,8 +23,10 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function Component(props) {
const t8 = props.value;
const x = t8;
let t20;
t20 = props.value;
const x = t20;
return x;
}
@@ -19,8 +19,9 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function Component(props) {
const t16 = props.a && props.b;
const x = t16;
let t38;
t38 = props.a && props.b;
const x = t38;
return x;
}
@@ -33,8 +33,8 @@ import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(3);
let t31 = undefined;
bb9: {
let t68;
bb10: {
const c_0 = $[0] !== props;
let y;
if (c_0) {
@@ -43,21 +43,21 @@ function Component(props) {
y.push(props.a);
}
if (props.cond2) {
t31 = y;
break bb9;
t68 = y;
break bb10;
}
y.push(props.b);
$[0] = props;
$[1] = y;
$[2] = t31;
$[2] = t68;
} else {
y = $[1];
t31 = $[2];
t68 = $[2];
}
t31 = y;
t68 = y;
}
const x = t31;
const x = t68;
return x;
}
@@ -24,10 +24,14 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function Component(props) {
let t31;
if (props.cond) {
if (props.cond) {
}
}
t31 = undefined;
const x = t31;
return x;
}
export const FIXTURE_ENTRYPOINT = {
@@ -15,7 +15,12 @@ function component(a) {
```javascript
function component(a) {
let t23;
mutate(a);
t23 = undefined;
const x = t23;
return x;
}
```
@@ -15,6 +15,7 @@ function component(a) {
import { unstable_useMemoCache as useMemoCache } from "react";
function component(a) {
const $ = useMemoCache(4);
let t24;
const c_0 = $[0] !== a;
let t0;
if (c_0) {
@@ -24,8 +25,8 @@ function component(a) {
} else {
t0 = $[1];
}
const t9 = t0;
const x = t9;
t24 = t0;
const x = t24;
const c_2 = $[2] !== x;
let t1;
if (c_2) {
@@ -28,17 +28,17 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function Component(props) {
let t17 = undefined;
bb8: switch (props.key) {
let t38;
bb9: switch (props.key) {
case "key": {
t17 = props.value;
break bb8;
t38 = props.value;
break bb9;
}
default: {
t17 = props.defaultValue;
t38 = props.defaultValue;
}
}
const x = t17;
const x = t38;
return x;
}
@@ -34,13 +34,13 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function Component(props) {
let t21 = undefined;
bb10: {
let t49;
bb11: {
let y = undefined;
bb2: switch (props.switch) {
case "foo": {
t21 = "foo";
break bb10;
t49 = "foo";
break bb11;
}
case "bar": {
y = "bar";
@@ -51,9 +51,9 @@ function Component(props) {
}
}
t21 = y;
t49 = y;
}
const x = t21;
const x = t49;
return x;
}