mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler] Add lowerContextAccess pass
*This is only for internal profiling, not intended to ship.*
This pass is intended to be used with https://github.com/facebook/react/pull/30407.
This pass synthesizes selector functions by collecting immediately
destructured context acesses. We bailout for other types of context
access.
This pass lowers context access to use a selector function by passing
the synthesized selector function as the second argument.
ghstack-source-id: 92d0f6ff2f
Pull Request resolved: https://github.com/facebook/react/pull/30548
This commit is contained in:
@@ -103,6 +103,7 @@ import {
|
||||
import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
|
||||
import {outlineFunctions} from '../Optimization/OutlineFunctions';
|
||||
import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
|
||||
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -204,6 +205,10 @@ function* runWithEnvironment(
|
||||
validateNoCapitalizedCalls(hir);
|
||||
}
|
||||
|
||||
if (env.config.enableLowerContextAccess) {
|
||||
lowerContextAccess(hir);
|
||||
}
|
||||
|
||||
analyseFunctions(hir);
|
||||
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* 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,
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
LoadLocal,
|
||||
Place,
|
||||
PropertyLoad,
|
||||
isUseContextHookType,
|
||||
makeBlockId,
|
||||
makeInstructionId,
|
||||
markInstructionIds,
|
||||
promoteTemporary,
|
||||
reversePostorderBlocks,
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace} from '../HIR/HIRBuilder';
|
||||
import {enterSSA} from '../SSA';
|
||||
import {inferTypes} from '../TypeInference';
|
||||
|
||||
export function lowerContextAccess(fn: HIRFunction): 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contextAccess.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)
|
||||
) {
|
||||
const keys = contextKeys.get(lvalue.identifier.id)!;
|
||||
const selectorFnInstr = emitSelectorFn(fn.env, keys);
|
||||
if (nextInstructions === null) {
|
||||
nextInstructions = block.instructions.slice(0, i);
|
||||
}
|
||||
nextInstructions.push(selectorFnInstr);
|
||||
|
||||
const selectorFn = selectorFnInstr.lvalue;
|
||||
value.args.push(selectorFn);
|
||||
}
|
||||
|
||||
if (nextInstructions) {
|
||||
nextInstructions.push(instr);
|
||||
}
|
||||
}
|
||||
if (nextInstructions) {
|
||||
block.instructions = nextInstructions;
|
||||
}
|
||||
}
|
||||
markInstructionIds(fn.body);
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
|
||||
const loadProp: PropertyLoad = {
|
||||
kind: 'PropertyLoad',
|
||||
object,
|
||||
property,
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
const element: Place = createTemporaryPlace(env, GeneratedSource);
|
||||
const loadPropInstr: Instruction = {
|
||||
lvalue: element,
|
||||
value: loadProp,
|
||||
id: makeInstructionId(0),
|
||||
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,
|
||||
},
|
||||
preds: new Set(),
|
||||
phis: new Set(),
|
||||
};
|
||||
|
||||
const fn: HIRFunction = {
|
||||
loc: GeneratedSource,
|
||||
id: null,
|
||||
fnType: 'Other',
|
||||
env,
|
||||
params: [obj],
|
||||
returnType: null,
|
||||
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,
|
||||
dependencies: [],
|
||||
},
|
||||
type: 'ArrowFunctionExpression',
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
lvalue: createTemporaryPlace(env, GeneratedSource),
|
||||
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,
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
return arrayInstr;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const {foo} = useContext(MyContext);
|
||||
const {bar} = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
|
||||
function App() {
|
||||
const $ = _c(3);
|
||||
const { foo } = useContext(MyContext, _temp);
|
||||
const { bar } = useContext(MyContext, _temp2);
|
||||
let t0;
|
||||
if ($[0] !== foo || $[1] !== bar) {
|
||||
t0 = <Bar foo={foo} bar={bar} />;
|
||||
$[0] = foo;
|
||||
$[1] = bar;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp2(t0) {
|
||||
return [t0.bar];
|
||||
}
|
||||
function _temp(t0) {
|
||||
return [t0.foo];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const {foo} = useContext(MyContext);
|
||||
const {bar} = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const {foo, bar} = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
|
||||
function App() {
|
||||
const $ = _c(3);
|
||||
const { foo, bar } = useContext(MyContext, _temp);
|
||||
let t0;
|
||||
if ($[0] !== foo || $[1] !== bar) {
|
||||
t0 = <Bar foo={foo} bar={bar} />;
|
||||
$[0] = foo;
|
||||
$[1] = bar;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp(t0) {
|
||||
return [t0.foo, t0.bar];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const {foo, bar} = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const [foo, bar] = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
|
||||
function App() {
|
||||
const $ = _c(3);
|
||||
const [foo, bar] = useContext(MyContext);
|
||||
let t0;
|
||||
if ($[0] !== foo || $[1] !== bar) {
|
||||
t0 = <Bar foo={foo} bar={bar} />;
|
||||
$[0] = foo;
|
||||
$[1] = bar;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const [foo, bar] = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const context = useContext(MyContext);
|
||||
const {foo} = context;
|
||||
const {bar} = context;
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
|
||||
function App() {
|
||||
const $ = _c(3);
|
||||
const context = useContext(MyContext);
|
||||
const { foo } = context;
|
||||
const { bar } = context;
|
||||
let t0;
|
||||
if ($[0] !== foo || $[1] !== bar) {
|
||||
t0 = <Bar foo={foo} bar={bar} />;
|
||||
$[0] = foo;
|
||||
$[1] = bar;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const context = useContext(MyContext);
|
||||
const {foo} = context;
|
||||
const {bar} = context;
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const context = useContext(MyContext);
|
||||
const [foo] = context;
|
||||
const {bar} = context;
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
|
||||
function App() {
|
||||
const $ = _c(3);
|
||||
const context = useContext(MyContext);
|
||||
const [foo] = context;
|
||||
const { bar } = context;
|
||||
let t0;
|
||||
if ($[0] !== foo || $[1] !== bar) {
|
||||
t0 = <Bar foo={foo} bar={bar} />;
|
||||
$[0] = foo;
|
||||
$[1] = bar;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const context = useContext(MyContext);
|
||||
const [foo] = context;
|
||||
const {bar} = context;
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const {
|
||||
joe: {foo},
|
||||
bar,
|
||||
} = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
|
||||
function App() {
|
||||
const $ = _c(3);
|
||||
const { joe: t0, bar } = useContext(MyContext);
|
||||
const { foo } = t0;
|
||||
let t1;
|
||||
if ($[0] !== foo || $[1] !== bar) {
|
||||
t1 = <Bar foo={foo} bar={bar} />;
|
||||
$[0] = foo;
|
||||
$[1] = bar;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const {
|
||||
joe: {foo},
|
||||
bar,
|
||||
} = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const context = useContext(MyContext);
|
||||
const foo = context.foo;
|
||||
const bar = context.bar;
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
|
||||
function App() {
|
||||
const $ = _c(3);
|
||||
const context = useContext(MyContext);
|
||||
const foo = context.foo;
|
||||
const bar = context.bar;
|
||||
let t0;
|
||||
if ($[0] !== foo || $[1] !== bar) {
|
||||
t0 = <Bar foo={foo} bar={bar} />;
|
||||
$[0] = foo;
|
||||
$[1] = bar;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// @enableLowerContextAccess
|
||||
function App() {
|
||||
const context = useContext(MyContext);
|
||||
const foo = context.foo;
|
||||
const bar = context.bar;
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
@@ -502,6 +502,15 @@ const skipFilter = new Set([
|
||||
|
||||
// needs to be executed as a module
|
||||
'meta-property',
|
||||
|
||||
// needs context lowering support in React
|
||||
'todo.lower-context-access-property-load',
|
||||
'todo.lower-context-access-nested-destructuring',
|
||||
'todo.lower-context-access-mixed-array-obj',
|
||||
'todo.lower-context-access-destructure-multiple',
|
||||
'todo.lower-context-access-array-destructuring',
|
||||
'lower-context-selector-simple',
|
||||
'lower-context-acess-multiple',
|
||||
]);
|
||||
|
||||
export default skipFilter;
|
||||
|
||||
Reference in New Issue
Block a user