mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
48e0c70292
ghstack-source-id: bb66913e2d3c814696311371ed655f3da03d1199 Pull Request resolved: https://github.com/facebook/react-forget/pull/2926
189 lines
6.1 KiB
TypeScript
189 lines
6.1 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import {
|
|
Effect,
|
|
HIRFunction,
|
|
Identifier,
|
|
InstructionId,
|
|
InstructionKind,
|
|
makeInstructionId,
|
|
Place,
|
|
} from "../HIR/HIR";
|
|
import { printPlace } from "../HIR/PrintHIR";
|
|
import {
|
|
eachInstructionLValue,
|
|
eachInstructionOperand,
|
|
eachTerminalOperand,
|
|
} from "../HIR/visitors";
|
|
import { assertExhaustive } from "../Utils/utils";
|
|
|
|
/*
|
|
* For each usage of a value in the given function, determines if the usage
|
|
* may be succeeded by a mutable usage of that same value and if so updates
|
|
* the usage to be mutable.
|
|
*
|
|
* Stated differently, this inference ensures that inferred capabilities of
|
|
* each reference are as follows:
|
|
* - freeze: the value is frozen at this point
|
|
* - readonly: the value is not modified at this point *or any subsequent
|
|
* point*
|
|
* - mutable: the value is modified at this point *or some subsequent point*.
|
|
*
|
|
* Note that this refines the capabilities inferered by InferReferenceCapability,
|
|
* which looks at individual references and not the lifetime of a value's mutability.
|
|
*
|
|
* == Algorithm
|
|
*
|
|
* TODO:
|
|
* 1. Forward data-flow analysis to determine aliasing. Unlike InferReferenceCapability
|
|
* which only tracks aliasing of top-level variables (`y = x`), this analysis needs
|
|
* to know if a value is aliased anywhere (`y.x = x`). The forward data flow tracks
|
|
* all possible locations which may have aliased a value. The concrete result is
|
|
* a mapping of each Place to the set of possibly-mutable values it may alias.
|
|
*
|
|
* ```
|
|
* const x = []; // {x: v0; v0: mutable []}
|
|
* const y = {}; // {x: v0, y: v1; v0: mutable [], v1: mutable []}
|
|
* y.x = x; // {x: v0, y: v1; v0: mutable [v1], v1: mutable [v0]}
|
|
* read(x); // {x: v0, y: v1; v0: mutable [v1], v1: mutable [v0]}
|
|
* mutate(y); // can infer that y mutates v0 and v1
|
|
* ```
|
|
*
|
|
* DONE:
|
|
* 2. Forward data-flow analysis to compute mutability liveness. Walk forwards over
|
|
* the CFG and track which values are mutated in a successor.
|
|
*
|
|
* ```
|
|
* mutate(y); // mutable y => v0, v1 mutated
|
|
* read(x); // x maps to v0, v1, those are in the mutated-later set, so x is mutable here
|
|
* ...
|
|
* ```
|
|
*/
|
|
|
|
function infer(place: Place, instrId: InstructionId): void {
|
|
place.identifier.mutableRange.end = makeInstructionId(instrId + 1);
|
|
}
|
|
|
|
function inferPlace(
|
|
place: Place,
|
|
instrId: InstructionId,
|
|
inferMutableRangeForStores: boolean
|
|
): void {
|
|
switch (place.effect) {
|
|
case Effect.Unknown: {
|
|
throw new Error(`Found an unknown place ${printPlace(place)}}!`);
|
|
}
|
|
case Effect.Capture:
|
|
case Effect.Read:
|
|
case Effect.Freeze:
|
|
return;
|
|
case Effect.Store:
|
|
if (inferMutableRangeForStores) {
|
|
infer(place, instrId);
|
|
}
|
|
return;
|
|
case Effect.ConditionallyMutate:
|
|
case Effect.Mutate: {
|
|
infer(place, instrId);
|
|
return;
|
|
}
|
|
default:
|
|
assertExhaustive(place.effect, `Unexpected ${printPlace(place)} effect`);
|
|
}
|
|
}
|
|
|
|
export function inferMutableLifetimes(
|
|
func: HIRFunction,
|
|
inferMutableRangeForStores: boolean
|
|
): void {
|
|
/*
|
|
* Context variables only appear to mutate where they are assigned, but we need
|
|
* to force their range to start at their declaration. Track the declaring instruction
|
|
* id so that the ranges can be extended if/when they are reassigned
|
|
*/
|
|
const contextVariableDeclarationInstructions = new Map<
|
|
Identifier,
|
|
InstructionId
|
|
>();
|
|
for (const [_, block] of func.body.blocks) {
|
|
for (const phi of block.phis) {
|
|
const isPhiMutatedAfterCreation: boolean =
|
|
phi.id.mutableRange.end >
|
|
(block.instructions.at(0)?.id ?? block.terminal.id);
|
|
if (
|
|
inferMutableRangeForStores &&
|
|
isPhiMutatedAfterCreation &&
|
|
phi.id.mutableRange.start === 0
|
|
) {
|
|
for (const [, operand] of phi.operands) {
|
|
if (phi.id.mutableRange.start === 0) {
|
|
phi.id.mutableRange.start = operand.mutableRange.start;
|
|
} else {
|
|
phi.id.mutableRange.start = makeInstructionId(
|
|
Math.min(phi.id.mutableRange.start, operand.mutableRange.start)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const instr of block.instructions) {
|
|
for (const operand of eachInstructionLValue(instr)) {
|
|
const lvalueId = operand.identifier;
|
|
|
|
/*
|
|
* lvalue start being mutable when they're initially assigned a
|
|
* value.
|
|
*/
|
|
lvalueId.mutableRange.start = instr.id;
|
|
|
|
/*
|
|
* Let's be optimistic and assume this lvalue is not mutable by
|
|
* default.
|
|
*/
|
|
lvalueId.mutableRange.end = makeInstructionId(instr.id + 1);
|
|
}
|
|
for (const operand of eachInstructionOperand(instr)) {
|
|
inferPlace(operand, instr.id, inferMutableRangeForStores);
|
|
}
|
|
|
|
if (
|
|
instr.value.kind === "DeclareContext" ||
|
|
(instr.value.kind === "StoreContext" &&
|
|
instr.value.lvalue.kind !== InstructionKind.Reassign)
|
|
) {
|
|
// Save declarations of context variables
|
|
contextVariableDeclarationInstructions.set(
|
|
instr.value.lvalue.place.identifier,
|
|
instr.id
|
|
);
|
|
} else if (instr.value.kind === "StoreContext") {
|
|
/*
|
|
* Else this is a reassignment, extend the range from the declaration (if present).
|
|
* Note that declarations may not be present for context variables that are reassigned
|
|
* within a function expression before (or without) a read of the same variable
|
|
*/
|
|
const declaration = contextVariableDeclarationInstructions.get(
|
|
instr.value.lvalue.place.identifier
|
|
);
|
|
if (declaration != null) {
|
|
const range = instr.value.lvalue.place.identifier.mutableRange;
|
|
if (range.start === 0) {
|
|
range.start = declaration;
|
|
} else {
|
|
range.start = makeInstructionId(Math.min(range.start, declaration));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const operand of eachTerminalOperand(block.terminal)) {
|
|
inferPlace(operand, block.terminal.id, inferMutableRangeForStores);
|
|
}
|
|
}
|
|
}
|