Skip to content

Commit 08ec181

Browse files
committed
[compiler] Add lowerContextAccess pass
*This is only for internal profiling, not intended to ship.* This pass is intended to be used with #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: 6a8d58e Pull Request resolved: #30548
1 parent bae18b4 commit 08ec181

16 files changed

+594
-0
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ import {
9898
} from '../Validation';
9999
import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
100100
import {outlineFunctions} from '../Optimization/OutlineFunctions';
101+
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
101102

102103
export type CompilerPipelineValue =
103104
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -199,6 +200,10 @@ function* runWithEnvironment(
199200
validateNoCapitalizedCalls(hir);
200201
}
201202

203+
if (env.config.enableLowerContextAccess) {
204+
lowerContextAccess(hir);
205+
}
206+
202207
analyseFunctions(hir);
203208
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
204209

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {
9+
ArrayExpression,
10+
BasicBlock,
11+
CallExpression,
12+
Destructure,
13+
Effect,
14+
Environment,
15+
GeneratedSource,
16+
HIRFunction,
17+
IdentifierId,
18+
Instruction,
19+
LoadLocal,
20+
Place,
21+
PropertyLoad,
22+
isUseContextHookType,
23+
makeBlockId,
24+
makeInstructionId,
25+
makeTemporary,
26+
markInstructionIds,
27+
promoteTemporary,
28+
reversePostorderBlocks,
29+
} from '../HIR';
30+
import {createTemporaryPlace} from '../HIR/HIRBuilder';
31+
import {enterSSA} from '../SSA';
32+
import {inferTypes} from '../TypeInference';
33+
34+
export function lowerContextAccess(fn: HIRFunction): void {
35+
const contextAccess: Map<IdentifierId, CallExpression> = new Map();
36+
const contextKeys: Map<IdentifierId, Array<string>> = new Map();
37+
38+
// collect context access and keys
39+
for (const [, block] of fn.body.blocks) {
40+
for (const instr of block.instructions) {
41+
const {value, lvalue} = instr;
42+
43+
if (
44+
value.kind === 'CallExpression' &&
45+
isUseContextHookType(value.callee.identifier)
46+
) {
47+
contextAccess.set(lvalue.identifier.id, value);
48+
continue;
49+
}
50+
51+
if (value.kind !== 'Destructure') {
52+
continue;
53+
}
54+
55+
const destructureId = value.value.identifier.id;
56+
if (!contextAccess.has(destructureId)) {
57+
continue;
58+
}
59+
60+
const keys = getContextKeys(value);
61+
if (keys === null) {
62+
return;
63+
}
64+
65+
if (contextKeys.has(destructureId)) {
66+
/*
67+
* TODO(gsn): Add support for accessing context over multiple
68+
* statements.
69+
*/
70+
return;
71+
} else {
72+
contextKeys.set(destructureId, keys);
73+
}
74+
}
75+
}
76+
77+
if (contextAccess.size > 0) {
78+
for (const [, block] of fn.body.blocks) {
79+
let nextInstructions: Array<Instruction> | null = null;
80+
81+
for (let i = 0; i < block.instructions.length; i++) {
82+
const instr = block.instructions[i];
83+
const {lvalue, value} = instr;
84+
if (
85+
value.kind === 'CallExpression' &&
86+
isUseContextHookType(value.callee.identifier) &&
87+
contextKeys.has(lvalue.identifier.id)
88+
) {
89+
const keys = contextKeys.get(lvalue.identifier.id)!;
90+
const selectorFnInstr = emitSelectorFn(fn.env, keys);
91+
if (nextInstructions === null) {
92+
nextInstructions = block.instructions.slice(0, i);
93+
}
94+
nextInstructions.push(selectorFnInstr);
95+
96+
const selectorFn = selectorFnInstr.lvalue;
97+
value.args.push(selectorFn);
98+
}
99+
100+
if (nextInstructions) {
101+
nextInstructions.push(instr);
102+
}
103+
}
104+
if (nextInstructions) {
105+
block.instructions = nextInstructions;
106+
}
107+
}
108+
markInstructionIds(fn.body);
109+
}
110+
}
111+
112+
function getContextKeys(value: Destructure): Array<string> | null {
113+
const keys = [];
114+
const pattern = value.lvalue.pattern;
115+
116+
switch (pattern.kind) {
117+
case 'ArrayPattern': {
118+
return null;
119+
}
120+
121+
case 'ObjectPattern': {
122+
for (const place of pattern.properties) {
123+
debugger;
124+
if (
125+
place.kind !== 'ObjectProperty' ||
126+
place.type !== 'property' ||
127+
place.key.kind !== 'identifier' ||
128+
place.place.identifier.name === null ||
129+
place.place.identifier.name.kind !== 'named'
130+
) {
131+
return null;
132+
}
133+
keys.push(place.key.name);
134+
}
135+
return keys;
136+
}
137+
}
138+
}
139+
140+
function emitPropertyLoad(
141+
env: Environment,
142+
obj: Place,
143+
property: string,
144+
): {instructions: Array<Instruction>; element: Place} {
145+
const loadObj: LoadLocal = {
146+
kind: 'LoadLocal',
147+
place: obj,
148+
loc: GeneratedSource,
149+
};
150+
const object: Place = createTemporaryPlace(env, GeneratedSource);
151+
const loadLocalInstr: Instruction = {
152+
lvalue: object,
153+
value: loadObj,
154+
id: makeInstructionId(0),
155+
loc: GeneratedSource,
156+
};
157+
158+
const loadProp: PropertyLoad = {
159+
kind: 'PropertyLoad',
160+
object,
161+
property,
162+
loc: GeneratedSource,
163+
};
164+
const element: Place = createTemporaryPlace(env, GeneratedSource);
165+
const loadPropInstr: Instruction = {
166+
lvalue: element,
167+
value: loadProp,
168+
id: makeInstructionId(0),
169+
loc: GeneratedSource,
170+
};
171+
return {
172+
instructions: [loadLocalInstr, loadPropInstr],
173+
element: element,
174+
};
175+
}
176+
177+
function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
178+
const obj: Place = createTemporaryPlace(env, GeneratedSource);
179+
promoteTemporary(obj.identifier);
180+
const instr: Array<Instruction> = [];
181+
const elements = [];
182+
for (const key of keys) {
183+
const {instructions, element: prop} = emitPropertyLoad(env, obj, key);
184+
instr.push(...instructions);
185+
elements.push(prop);
186+
}
187+
188+
const arrayInstr = emitArrayInstr(elements, env);
189+
instr.push(arrayInstr);
190+
191+
const block: BasicBlock = {
192+
kind: 'block',
193+
id: makeBlockId(0),
194+
instructions: instr,
195+
terminal: {
196+
id: makeInstructionId(0),
197+
kind: 'return',
198+
loc: GeneratedSource,
199+
value: arrayInstr.lvalue,
200+
},
201+
preds: new Set(),
202+
phis: new Set(),
203+
};
204+
205+
const fn: HIRFunction = {
206+
loc: GeneratedSource,
207+
id: null,
208+
fnType: 'Other',
209+
env,
210+
params: [obj],
211+
returnType: null,
212+
context: [],
213+
effects: null,
214+
body: {
215+
entry: block.id,
216+
blocks: new Map([[block.id, block]]),
217+
},
218+
generator: false,
219+
async: false,
220+
directives: [],
221+
};
222+
223+
reversePostorderBlocks(fn.body);
224+
markInstructionIds(fn.body);
225+
enterSSA(fn);
226+
inferTypes(fn);
227+
228+
const fnInstr: Instruction = {
229+
id: makeInstructionId(0),
230+
value: {
231+
kind: 'FunctionExpression',
232+
name: null,
233+
loweredFunc: {
234+
func: fn,
235+
dependencies: [],
236+
},
237+
type: 'ArrowFunctionExpression',
238+
loc: GeneratedSource,
239+
},
240+
lvalue: {
241+
kind: 'Identifier',
242+
identifier: makeTemporary(env.nextIdentifierId, GeneratedSource),
243+
effect: Effect.Unknown,
244+
reactive: false,
245+
loc: GeneratedSource,
246+
},
247+
loc: GeneratedSource,
248+
};
249+
return fnInstr;
250+
}
251+
252+
function emitArrayInstr(elements: Array<Place>, env: Environment): Instruction {
253+
const array: ArrayExpression = {
254+
kind: 'ArrayExpression',
255+
elements,
256+
loc: GeneratedSource,
257+
};
258+
const arrayLvalue: Place = {
259+
kind: 'Identifier',
260+
identifier: makeTemporary(env.nextIdentifierId, GeneratedSource),
261+
effect: Effect.Unknown,
262+
reactive: false,
263+
loc: GeneratedSource,
264+
};
265+
const arrayInstr: Instruction = {
266+
id: makeInstructionId(0),
267+
value: array,
268+
lvalue: arrayLvalue,
269+
loc: GeneratedSource,
270+
};
271+
return arrayInstr;
272+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @enableLowerContextAccess
6+
function App() {
7+
const {foo} = useContext(MyContext);
8+
const {bar} = useContext(MyContext);
9+
return <Bar foo={foo} bar={bar} />;
10+
}
11+
12+
```
13+
14+
## Code
15+
16+
```javascript
17+
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
18+
function App() {
19+
const $ = _c(3);
20+
const { foo } = useContext(MyContext, _temp);
21+
const { bar } = useContext(MyContext, _temp2);
22+
let t0;
23+
if ($[0] !== foo || $[1] !== bar) {
24+
t0 = <Bar foo={foo} bar={bar} />;
25+
$[0] = foo;
26+
$[1] = bar;
27+
$[2] = t0;
28+
} else {
29+
t0 = $[2];
30+
}
31+
return t0;
32+
}
33+
function _temp2(t0) {
34+
return [t0.bar];
35+
}
36+
function _temp(t0) {
37+
return [t0.foo];
38+
}
39+
40+
```
41+
42+
### Eval output
43+
(kind: exception) Fixture not implemented
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @enableLowerContextAccess
2+
function App() {
3+
const {foo} = useContext(MyContext);
4+
const {bar} = useContext(MyContext);
5+
return <Bar foo={foo} bar={bar} />;
6+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @enableLowerContextAccess
6+
function App() {
7+
const {foo, bar} = useContext(MyContext);
8+
return <Bar foo={foo} bar={bar} />;
9+
}
10+
11+
```
12+
13+
## Code
14+
15+
```javascript
16+
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
17+
function App() {
18+
const $ = _c(3);
19+
const { foo, bar } = useContext(MyContext, _temp);
20+
let t0;
21+
if ($[0] !== foo || $[1] !== bar) {
22+
t0 = <Bar foo={foo} bar={bar} />;
23+
$[0] = foo;
24+
$[1] = bar;
25+
$[2] = t0;
26+
} else {
27+
t0 = $[2];
28+
}
29+
return t0;
30+
}
31+
function _temp(t0) {
32+
return [t0.foo, t0.bar];
33+
}
34+
35+
```
36+
37+
### Eval output
38+
(kind: exception) Fixture not implemented
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// @enableLowerContextAccess
2+
function App() {
3+
const {foo, bar} = useContext(MyContext);
4+
return <Bar foo={foo} bar={bar} />;
5+
}

0 commit comments

Comments
 (0)