First cut of inferring effect deps

This commit is contained in:
Jordan Brown
2024-11-19 11:51:00 -05:00
parent 469ae32692
commit 3bd6f87106
5 changed files with 176 additions and 0 deletions
@@ -36,6 +36,7 @@ import {
inferReactivePlaces,
inferReferenceEffects,
inlineImmediatelyInvokedFunctionExpressions,
inferEffectDependencies,
} from '../Inference';
import {
constantPropagation,
@@ -353,6 +354,10 @@ function* runWithEnvironment(
name: 'PropagateScopeDependenciesHIR',
value: hir,
});
if (env.config.EXPERIMENTAL_inferEffectDependencies) {
inferEffectDependencies(env, hir);
}
if (env.config.inlineJsxTransform) {
inlineJsxTransform(hir, env.config.inlineJsxTransform);
@@ -0,0 +1,60 @@
import { ArrayExpression, Effect, Environment, FunctionExpression, GeneratedSource, HIRFunction, IdentifierId, Instruction, isUseEffectHookType, makeInstructionId } from "../HIR";
import { createTemporaryPlace } from "../HIR/HIRBuilder";
export function inferEffectDependencies(
env: Environment,
fn: HIRFunction,
): void {
const fnExpressions = new Map<IdentifierId, FunctionExpression>();
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
const {value, lvalue} = instr;
if (
value.kind === 'FunctionExpression'
) {
fnExpressions.set(lvalue.identifier.id, value)
}
}
}
for (const [, block] of fn.body.blocks) {
let newInstructions = [...block.instructions];
let addedInstrs = 0;
for (const [idx, instr] of block.instructions.entries()) {
const {value} = instr;
/*
* This check is not final. Right now we only look for useEffects without a dependency array.
* This is likely not how we will ship this feature, but it is good enough for us to make progress
* on the implementation and test it.
*/
if (
value.kind === 'CallExpression' &&
isUseEffectHookType(value.callee.identifier) &&
value.args[0].kind === 'Identifier' &&
value.args.length === 1
) {
const fnExpr = fnExpressions.get(value.args[0].identifier.id);
if (fnExpr != null) {
const deps: ArrayExpression = {
kind: "ArrayExpression",
elements: [...fnExpr.loweredFunc.dependencies],
loc: GeneratedSource
};
const depsPlace = createTemporaryPlace(env, GeneratedSource);
depsPlace.effect = Effect.Read;
const newInstruction: Instruction = {
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: depsPlace,
value: deps,
};
newInstructions.splice(idx + addedInstrs, 0, newInstruction);
addedInstrs++;
value.args[1] = depsPlace;
}
}
}
block.instructions = newInstructions;
}
}
@@ -11,3 +11,4 @@ export {inferMutableRanges} from './InferMutableRanges';
export {inferReactivePlaces} from './InferReactivePlaces';
export {default as inferReferenceEffects} from './InferReferenceEffects';
export {inlineImmediatelyInvokedFunctionExpressions} from './InlineImmediatelyInvokedFunctionExpressions';
export {inferEffectDependencies} from './InferEffectDependencies';
@@ -0,0 +1,86 @@
## Input
```javascript
// @inferEffectDependencies
const nonreactive = 0;
function Component({foo, bar}) {
useEffect(() => {
console.log(foo);
console.log(bar);
console.log(nonreactive);
});
useEffect(() => {
console.log(foo);
console.log(bar?.baz);
console.log(bar.qux);
});
function f() {
console.log(foo);
}
useEffect(f);
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
const nonreactive = 0;
function Component(t0) {
const $ = _c(8);
const { foo, bar } = t0;
let t1;
if ($[0] !== foo || $[1] !== bar) {
t1 = () => {
console.log(foo);
console.log(bar);
console.log(nonreactive);
};
$[0] = foo;
$[1] = bar;
$[2] = t1;
} else {
t1 = $[2];
}
useEffect(t1, [foo, bar]);
let t2;
if ($[3] !== foo || $[4] !== bar) {
t2 = () => {
console.log(foo);
console.log(bar?.baz);
console.log(bar.qux);
};
$[3] = foo;
$[4] = bar;
$[5] = t2;
} else {
t2 = $[5];
}
useEffect(t2, [foo, bar, bar.qux]);
let t3;
if ($[6] !== foo) {
t3 = function f() {
console.log(foo);
};
$[6] = foo;
$[7] = t3;
} else {
t3 = $[7];
}
const f = t3;
useEffect(f);
}
```
### Eval output
(kind: exception) Fixture not implemented
@@ -0,0 +1,24 @@
// @inferEffectDependencies
const nonreactive = 0;
function Component({foo, bar}) {
useEffect(() => {
console.log(foo);
console.log(bar);
console.log(nonreactive);
});
useEffect(() => {
console.log(foo);
console.log(bar?.baz);
console.log(bar.qux);
});
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
}