mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Update on "[compiler] Separate InferFunctionEffects pass from InferReferenceEffects"
Test Plan:
This diff finally separates InferFunctionEffects into its own separate pass from InferReferenceEffects. It relies on the abstractValues populated in IRE as well as the alias sets that IRE returns.
The meat of the InferFunctionEffects algorithm is still the same, but rather than querying the "live" InferenceState from IRE for abstract values and alias information, we query the values defined on places and the alias set returned by IRE.
One extra bit of work that we now need to perform is creating a map from IdentifierIds to AbstractValues, which we do by traversing the HIRFunction and examining all places. We also need to track the computed effects of nested functions, and that's where the disjoint set of aliases comes in -- when performing the main algorithm, we might see a set of instructions like
```
$0 = Function (effect=ContextMutation) { ... }
$1 = LoadLocal $0
$2 = Call $1 ()
```
When examining the `Call` instruction, we need to know that $1 has the function effect [ContextMutation]. Since $0 and $1 were inferred to be aliased by IRE, we don't need to do any other propagation from $0 to $1 if we track the nested effects based on the root of the alias set { $0, $1 } rather than the specific identifier $1 present in the Call instruction.
[ghstack-poisoned]
This commit is contained in:
+91
-64
@@ -9,6 +9,7 @@ import {CompilerError, ErrorSeverity, ValueKind} from '..';
|
||||
import {
|
||||
AbstractValue,
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
Effect,
|
||||
Environment,
|
||||
FunctionEffect,
|
||||
@@ -30,7 +31,7 @@ import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
type State = {
|
||||
values: Map<IdentifierId, AbstractValue>;
|
||||
nestedEffects: Map<IdentifierId, Array<FunctionEffect>>;
|
||||
nestedEffects: Map<IdentifierId, Set<FunctionEffect>>;
|
||||
aliases: DisjointSet<IdentifierId>;
|
||||
};
|
||||
|
||||
@@ -63,41 +64,99 @@ export function inferFunctionEffects(
|
||||
});
|
||||
state.values.set(cx.identifier.id, cx.abstractValue);
|
||||
}
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
for (const phi of block.phis) {
|
||||
CompilerError.invariant(phi.abstractValue != null, {
|
||||
reason: 'Expected phi to have a kind',
|
||||
loc: phi.id.loc,
|
||||
});
|
||||
state.values.set(phi.id.id, phi.abstractValue);
|
||||
}
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
/*
|
||||
* Since we infer the effects of nested functions from function
|
||||
* instructions, we need to make sure we can go from identifiers
|
||||
* aliased to function definitions to the function's effects
|
||||
*/
|
||||
const root =
|
||||
state.aliases.find(instr.lvalue.identifier.id) ??
|
||||
instr.lvalue.identifier.id;
|
||||
CompilerError.invariant(root != null, {
|
||||
reason: 'Expected lvalue to have a root',
|
||||
loc: instr.loc,
|
||||
let blocksSeen: Set<BlockId> = new Set();
|
||||
let backEdgeSeen = false;
|
||||
let lastNested: Map<IdentifierId, Set<FunctionEffect>>;
|
||||
|
||||
do {
|
||||
lastNested = new Map(state.nestedEffects);
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
blocksSeen.add(blockId);
|
||||
for (const phi of block.phis) {
|
||||
CompilerError.invariant(phi.abstractValue != null, {
|
||||
reason: 'Expected phi to have a kind',
|
||||
loc: phi.id.loc,
|
||||
});
|
||||
const nested = state.nestedEffects.get(root) ?? [];
|
||||
nested.push(...(instr.value.loweredFunc.func.effects ?? []));
|
||||
state.nestedEffects.set(root, nested);
|
||||
const phiRoot = state.aliases.find(phi.id.id) ?? phi.id.id;
|
||||
for (const [predId, operand] of phi.operands) {
|
||||
if (!blocksSeen.has(predId)) {
|
||||
backEdgeSeen = true;
|
||||
}
|
||||
const operandRoot = state.aliases.find(operand.id) ?? operand.id;
|
||||
if (state.nestedEffects.has(operandRoot)) {
|
||||
state.nestedEffects.set(
|
||||
phiRoot,
|
||||
state.nestedEffects.get(operandRoot) ?? new Set(),
|
||||
);
|
||||
}
|
||||
}
|
||||
state.values.set(phi.id.id, phi.abstractValue);
|
||||
}
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
lvalue.abstractValue &&
|
||||
state.values.set(lvalue.identifier.id, lvalue.abstractValue);
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
/**
|
||||
* If this function references other functions, propagate the referenced function's
|
||||
* effects to this function.
|
||||
*
|
||||
* ```
|
||||
* let f = () => global = true;
|
||||
* let g = () => f();
|
||||
* g();
|
||||
* ```
|
||||
*
|
||||
* In this example, because `g` references `f`, we propagate the GlobalMutation from
|
||||
* `f` to `g`. Thus, referencing `g` in `g()` will evaluate the GlobalMutation in the outer
|
||||
* function effect context and report an error. But if instead we do:
|
||||
*
|
||||
* ```
|
||||
* let f = () => global = true;
|
||||
* let g = () => f();
|
||||
* useEffect(() => g(), [g])
|
||||
* ```
|
||||
*
|
||||
* Now `g`'s effects will be discarded since they're in a useEffect.
|
||||
*/
|
||||
let propagatedEffects = [
|
||||
...(instr.value.loweredFunc.func.effects ?? []),
|
||||
];
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
propagatedEffects.push(
|
||||
...inferFunctionInstrEffects(state, operand),
|
||||
);
|
||||
}
|
||||
/*
|
||||
* Since we infer the effects of nested functions from function
|
||||
* instructions, we need to make sure we can go from identifiers
|
||||
* aliased to function definitions to the function's effects
|
||||
*/
|
||||
const root =
|
||||
state.aliases.find(instr.lvalue.identifier.id) ??
|
||||
instr.lvalue.identifier.id;
|
||||
CompilerError.invariant(root != null, {
|
||||
reason: 'Expected lvalue to have a root',
|
||||
loc: instr.loc,
|
||||
});
|
||||
const curEffects = state.nestedEffects.get(root) ?? new Set();
|
||||
if (propagatedEffects.some(eff => !curEffects.has(eff))) {
|
||||
const nested = new Set([...curEffects, ...propagatedEffects]);
|
||||
state.nestedEffects.set(root, nested);
|
||||
}
|
||||
instr.value.loweredFunc.func.effects = propagatedEffects;
|
||||
}
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
lvalue.abstractValue &&
|
||||
state.values.set(lvalue.identifier.id, lvalue.abstractValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Loop until we have propagated all nested effects backwards through backedges, if they exist
|
||||
} while (
|
||||
backEdgeSeen &&
|
||||
![...state.nestedEffects].every(([id, effs]) => lastNested.get(id) === effs)
|
||||
);
|
||||
|
||||
const functionEffects: Array<FunctionEffect> = [];
|
||||
|
||||
@@ -277,38 +336,6 @@ export function inferInstructionFunctionEffects(
|
||||
}
|
||||
case 'ObjectMethod':
|
||||
case 'FunctionExpression': {
|
||||
/**
|
||||
* If this function references other functions, propagate the referenced function's
|
||||
* effects to this function.
|
||||
*
|
||||
* ```
|
||||
* let f = () => global = true;
|
||||
* let g = () => f();
|
||||
* g();
|
||||
* ```
|
||||
*
|
||||
* In this example, because `g` references `f`, we propagate the GlobalMutation from
|
||||
* `f` to `g`. Thus, referencing `g` in `g()` will evaluate the GlobalMutation in the outer
|
||||
* function effect context and report an error. But if instead we do:
|
||||
*
|
||||
* ```
|
||||
* let f = () => global = true;
|
||||
* let g = () => f();
|
||||
* useEffect(() => g(), [g])
|
||||
* ```
|
||||
*
|
||||
* Now `g`'s effects will be discarded since they're in a useEffect.
|
||||
*/
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
const funEffects = inferFunctionInstrEffects(state, operand);
|
||||
instr.value.loweredFunc.func.effects ??= [];
|
||||
instr.value.loweredFunc.func.effects.push(...funEffects);
|
||||
state.nestedEffects.set(
|
||||
state.aliases.find(instr.lvalue.identifier.id) ??
|
||||
instr.lvalue.identifier.id,
|
||||
instr.value.loweredFunc.func.effects,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'MethodCall':
|
||||
|
||||
@@ -240,7 +240,6 @@ export default function inferReferenceEffects(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompilerError.invariant(finishedStates.size > 0, {
|
||||
reason: 'Expected to have processed at least one block',
|
||||
loc: null,
|
||||
|
||||
Reference in New Issue
Block a user