Files
react/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts
Joseph Savona 66cfe048d3 [compiler] New mutability/aliasing model (#33494)
Squashed, review-friendly version of the stack from
https://github.com/facebook/react/pull/33488.

This is new version of our mutability and inference model, designed to
replace the core algorithm for determining the sets of instructions
involved in constructing a given value or set of values. The new model
replaces InferReferenceEffects, InferMutableRanges (and all of its
subcomponents), and parts of AnalyzeFunctions. The new model does not
use per-Place effect values, but in order to make this drop-in the end
_result_ of the inference adds these per-Place effects.

I'll write up a larger document on the model, first i'm doing some
housekeeping to rebase the PR.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33494).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* #33497
* #33496
* #33495
* __->__ #33494
* #33572
2025-06-18 12:58:06 -07:00

308 lines
7.6 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 {
ArrayExpression,
BasicBlock,
CallExpression,
Destructure,
Environment,
ExternalFunction,
GeneratedSource,
HIRFunction,
IdentifierId,
Instruction,
LoadGlobal,
LoadLocal,
NonLocalImportSpecifier,
Place,
PropertyLoad,
isUseContextHookType,
makeBlockId,
makeInstructionId,
makePropertyLiteral,
makeType,
markInstructionIds,
promoteTemporary,
reversePostorderBlocks,
} from '../HIR';
import {createTemporaryPlace} from '../HIR/HIRBuilder';
import {enterSSA} from '../SSA';
import {inferTypes} from '../TypeInference';
export function lowerContextAccess(
fn: HIRFunction,
loweredContextCalleeConfig: ExternalFunction,
): void {
const contextAccess: Map<IdentifierId, CallExpression> = new Map();
const contextKeys: Map<IdentifierId, Array<string>> = new Map();
// collect context access and keys
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
const {value, lvalue} = instr;
if (
value.kind === 'CallExpression' &&
isUseContextHookType(value.callee.identifier)
) {
contextAccess.set(lvalue.identifier.id, value);
continue;
}
if (value.kind !== 'Destructure') {
continue;
}
const destructureId = value.value.identifier.id;
if (!contextAccess.has(destructureId)) {
continue;
}
const keys = getContextKeys(value);
if (keys === null) {
return;
}
if (contextKeys.has(destructureId)) {
/*
* TODO(gsn): Add support for accessing context over multiple
* statements.
*/
return;
} else {
contextKeys.set(destructureId, keys);
}
}
}
let importLoweredContextCallee: NonLocalImportSpecifier | null = null;
if (contextAccess.size > 0 && contextKeys.size > 0) {
for (const [, block] of fn.body.blocks) {
let nextInstructions: Array<Instruction> | null = null;
for (let i = 0; i < block.instructions.length; i++) {
const instr = block.instructions[i];
const {lvalue, value} = instr;
if (
value.kind === 'CallExpression' &&
isUseContextHookType(value.callee.identifier) &&
contextKeys.has(lvalue.identifier.id)
) {
importLoweredContextCallee ??=
fn.env.programContext.addImportSpecifier(
loweredContextCalleeConfig,
);
const loweredContextCalleeInstr = emitLoadLoweredContextCallee(
fn.env,
importLoweredContextCallee,
);
if (nextInstructions === null) {
nextInstructions = block.instructions.slice(0, i);
}
nextInstructions.push(loweredContextCalleeInstr);
const keys = contextKeys.get(lvalue.identifier.id)!;
const selectorFnInstr = emitSelectorFn(fn.env, keys);
nextInstructions.push(selectorFnInstr);
const lowerContextCallId = loweredContextCalleeInstr.lvalue;
value.callee = lowerContextCallId;
const selectorFn = selectorFnInstr.lvalue;
value.args.push(selectorFn);
}
if (nextInstructions) {
nextInstructions.push(instr);
}
}
if (nextInstructions) {
block.instructions = nextInstructions;
}
}
markInstructionIds(fn.body);
inferTypes(fn);
}
}
function emitLoadLoweredContextCallee(
env: Environment,
importedLowerContextCallee: NonLocalImportSpecifier,
): Instruction {
const loadGlobal: LoadGlobal = {
kind: 'LoadGlobal',
binding: {...importedLowerContextCallee},
loc: GeneratedSource,
};
return {
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: createTemporaryPlace(env, GeneratedSource),
effects: null,
value: loadGlobal,
};
}
function getContextKeys(value: Destructure): Array<string> | null {
const keys = [];
const pattern = value.lvalue.pattern;
switch (pattern.kind) {
case 'ArrayPattern': {
return null;
}
case 'ObjectPattern': {
for (const place of pattern.properties) {
if (
place.kind !== 'ObjectProperty' ||
place.type !== 'property' ||
place.key.kind !== 'identifier' ||
place.place.identifier.name === null ||
place.place.identifier.name.kind !== 'named'
) {
return null;
}
keys.push(place.key.name);
}
return keys;
}
}
}
function emitPropertyLoad(
env: Environment,
obj: Place,
property: string,
): {instructions: Array<Instruction>; element: Place} {
const loadObj: LoadLocal = {
kind: 'LoadLocal',
place: obj,
loc: GeneratedSource,
};
const object: Place = createTemporaryPlace(env, GeneratedSource);
const loadLocalInstr: Instruction = {
lvalue: object,
value: loadObj,
id: makeInstructionId(0),
effects: null,
loc: GeneratedSource,
};
const loadProp: PropertyLoad = {
kind: 'PropertyLoad',
object,
property: makePropertyLiteral(property),
loc: GeneratedSource,
};
const element: Place = createTemporaryPlace(env, GeneratedSource);
const loadPropInstr: Instruction = {
lvalue: element,
value: loadProp,
id: makeInstructionId(0),
effects: null,
loc: GeneratedSource,
};
return {
instructions: [loadLocalInstr, loadPropInstr],
element: element,
};
}
function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
const obj: Place = createTemporaryPlace(env, GeneratedSource);
promoteTemporary(obj.identifier);
const instr: Array<Instruction> = [];
const elements = [];
for (const key of keys) {
const {instructions, element: prop} = emitPropertyLoad(env, obj, key);
instr.push(...instructions);
elements.push(prop);
}
const arrayInstr = emitArrayInstr(elements, env);
instr.push(arrayInstr);
const block: BasicBlock = {
kind: 'block',
id: makeBlockId(0),
instructions: instr,
terminal: {
id: makeInstructionId(0),
kind: 'return',
loc: GeneratedSource,
value: arrayInstr.lvalue,
effects: null,
},
preds: new Set(),
phis: new Set(),
};
const fn: HIRFunction = {
loc: GeneratedSource,
id: null,
fnType: 'Other',
env,
params: [obj],
returnTypeAnnotation: null,
returnType: makeType(),
returns: createTemporaryPlace(env, GeneratedSource),
context: [],
effects: null,
body: {
entry: block.id,
blocks: new Map([[block.id, block]]),
},
generator: false,
async: false,
directives: [],
};
reversePostorderBlocks(fn.body);
markInstructionIds(fn.body);
enterSSA(fn);
inferTypes(fn);
const fnInstr: Instruction = {
id: makeInstructionId(0),
value: {
kind: 'FunctionExpression',
name: null,
loweredFunc: {
func: fn,
},
type: 'ArrowFunctionExpression',
loc: GeneratedSource,
},
lvalue: createTemporaryPlace(env, GeneratedSource),
effects: null,
loc: GeneratedSource,
};
return fnInstr;
}
function emitArrayInstr(elements: Array<Place>, env: Environment): Instruction {
const array: ArrayExpression = {
kind: 'ArrayExpression',
elements,
loc: GeneratedSource,
};
const arrayLvalue: Place = createTemporaryPlace(env, GeneratedSource);
const arrayInstr: Instruction = {
id: makeInstructionId(0),
value: array,
lvalue: arrayLvalue,
effects: null,
loc: GeneratedSource,
};
return arrayInstr;
}