Skip to content

Commit

Permalink
[compiler] Rewrite useContext callee
Browse files Browse the repository at this point in the history
If a value is specified for the LowerContextAccess environment config,
we rewrite the callee from 'useContext' to the specificed value.

This will allow us run an experiment internally.

ghstack-source-id: c305c4dffd4a9b794320ded734520ff35ade05db
Pull Request resolved: #30612
  • Loading branch information
gsathya committed Aug 6, 2024
1 parent 89e2305 commit 505b35c
Show file tree
Hide file tree
Showing 19 changed files with 95 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ function* runWithEnvironment(
validateNoCapitalizedCalls(hir);
}

if (env.config.enableLowerContextAccess) {
lowerContextAccess(hir);
if (env.config.lowerContextAccess) {
lowerContextAccess(hir, env.config.lowerContextAccess);
}

analyseFunctions(hir);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,11 @@ export function compileProgram(
externalFunctions.push(gating);
}

const lowerContextAccess = pass.opts.environment?.lowerContextAccess;
if (lowerContextAccess) {
externalFunctions.push(tryParseExternalFunction(lowerContextAccess));
}

const enableEmitInstrumentForget =
pass.opts.environment?.enableEmitInstrumentForget;
if (enableEmitInstrumentForget != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,8 +436,11 @@ const EnvironmentConfigSchema = z.object({
enableTreatRefLikeIdentifiersAsRefs: z.boolean().nullable().default(false),

/*
* If enabled, this lowers any calls to `useContext` hook to use a selector
* function.
* If specified a value, the compiler lowers any calls to `useContext` to use
* this value as the callee.
*
* A selector function is compiled and passed as an argument along with the
* context to this function call.
*
* The compiler automatically figures out the keys by looking for the immediate
* destructuring of the return value from the useContext call. In the future,
Expand All @@ -449,10 +452,10 @@ const EnvironmentConfigSchema = z.object({
* const {foo, bar} = useContext(MyContext);
*
* // output
* const {foo, bar} = useContext(MyContext, (c) => [c.foo, c.bar]);
* const {foo, bar} = useCompiledContext(MyContext, (c) => [c.foo, c.bar]);
* ```
*/
enableLowerContextAccess: z.boolean().nullable().default(false),
lowerContextAccess: ExternalFunctionSchema.nullish(),
});

export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import {
CallExpression,
Destructure,
Environment,
ExternalFunction,
GeneratedSource,
HIRFunction,
IdentifierId,
Instruction,
LoadGlobal,
LoadLocal,
Place,
PropertyLoad,
Expand All @@ -29,7 +31,10 @@ import {createTemporaryPlace} from '../HIR/HIRBuilder';
import {enterSSA} from '../SSA';
import {inferTypes} from '../TypeInference';

export function lowerContextAccess(fn: HIRFunction): void {
export function lowerContextAccess(
fn: HIRFunction,
loweredContextCallee: ExternalFunction,
): void {
const contextAccess: Map<IdentifierId, CallExpression> = new Map();
const contextKeys: Map<IdentifierId, Array<string>> = new Map();

Expand Down Expand Up @@ -84,13 +89,23 @@ export function lowerContextAccess(fn: HIRFunction): void {
isUseContextHookType(value.callee.identifier) &&
contextKeys.has(lvalue.identifier.id)
) {
const keys = contextKeys.get(lvalue.identifier.id)!;
const selectorFnInstr = emitSelectorFn(fn.env, keys);
const loweredContextCalleeInstr = emitLoadLoweredContextCallee(
fn.env,
loweredContextCallee,
);

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);
}
Expand All @@ -104,9 +119,32 @@ export function lowerContextAccess(fn: HIRFunction): void {
}
}
markInstructionIds(fn.body);
inferTypes(fn);
}
}

function emitLoadLoweredContextCallee(
env: Environment,
loweredContextCallee: ExternalFunction,
): Instruction {
const loadGlobal: LoadGlobal = {
kind: 'LoadGlobal',
binding: {
kind: 'ImportNamespace',
module: loweredContextCallee.source,
name: loweredContextCallee.importSpecifierName,
},
loc: GeneratedSource,
};

return {
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: createTemporaryPlace(env, GeneratedSource),
value: loadGlobal,
};
}

function getContextKeys(value: Destructure): Array<string> | null {
const keys = [];
const pattern = value.lvalue.pattern;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const {foo} = useContext(MyContext);
const {bar} = useContext(MyContext);
Expand All @@ -14,11 +14,12 @@ function App() {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
import { useContext_withSelector } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess
function App() {
const $ = _c(3);
const { foo } = useContext(MyContext, _temp);
const { bar } = useContext(MyContext, _temp2);
const { foo } = useContext_withSelector(MyContext, _temp);
const { bar } = useContext_withSelector(MyContext, _temp2);
let t0;
if ($[0] !== foo || $[1] !== bar) {
t0 = <Bar foo={foo} bar={bar} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const {foo} = useContext(MyContext);
const {bar} = useContext(MyContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const {foo, bar} = useContext(MyContext);
return <Bar foo={foo} bar={bar} />;
Expand All @@ -13,10 +13,11 @@ function App() {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
import { useContext_withSelector } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess
function App() {
const $ = _c(3);
const { foo, bar } = useContext(MyContext, _temp);
const { foo, bar } = useContext_withSelector(MyContext, _temp);
let t0;
if ($[0] !== foo || $[1] !== bar) {
t0 = <Bar foo={foo} bar={bar} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const {foo, bar} = useContext(MyContext);
return <Bar foo={foo} bar={bar} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const [foo, bar] = useContext(MyContext);
return <Bar foo={foo} bar={bar} />;
Expand All @@ -13,7 +13,8 @@ function App() {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
import { useContext_withSelector } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess
function App() {
const $ = _c(3);
const [foo, bar] = useContext(MyContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const [foo, bar] = useContext(MyContext);
return <Bar foo={foo} bar={bar} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const context = useContext(MyContext);
const {foo} = context;
Expand All @@ -15,7 +15,8 @@ function App() {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
import { useContext_withSelector } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess
function App() {
const $ = _c(3);
const context = useContext(MyContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const context = useContext(MyContext);
const {foo} = context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const context = useContext(MyContext);
const [foo] = context;
Expand All @@ -15,7 +15,8 @@ function App() {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
import { useContext_withSelector } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess
function App() {
const $ = _c(3);
const context = useContext(MyContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const context = useContext(MyContext);
const [foo] = context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const {
joe: {foo},
Expand All @@ -16,7 +16,8 @@ function App() {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
import { useContext_withSelector } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess
function App() {
const $ = _c(3);
const { joe: t0, bar } = useContext(MyContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const {
joe: {foo},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const context = useContext(MyContext);
const foo = context.foo;
Expand All @@ -15,7 +15,8 @@ function App() {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @enableLowerContextAccess
import { useContext_withSelector } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess
function App() {
const $ = _c(3);
const context = useContext(MyContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableLowerContextAccess
// @lowerContextAccess
function App() {
const context = useContext(MyContext);
const foo = context.foo;
Expand Down
9 changes: 9 additions & 0 deletions compiler/packages/snap/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ function makePluginOptions(
.filter(s => s.length > 0);
}

let lowerContextAccess = null;
if (firstLine.includes('@lowerContextAccess')) {
lowerContextAccess = {
source: 'react-compiler-runtime',
importSpecifierName: 'useContext_withSelector',
};
}

let logs: Array<{filename: string | null; event: LoggerEvent}> = [];
let logger: Logger | null = null;
if (firstLine.includes('@logger')) {
Expand Down Expand Up @@ -207,6 +215,7 @@ function makePluginOptions(
hookPattern,
validatePreserveExistingMemoizationGuarantees,
enableChangeDetectionForDebugging,
lowerContextAccess,
},
compilationMode,
logger,
Expand Down

0 comments on commit 505b35c

Please sign in to comment.