Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[compiler] Rewrite useContext callee #30612

Merged
merged 6 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name doesn't match the name of the generated import

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah because it's not lowered here -- note the todo in the filename

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i need to keep some state to check if we lower or not and propagate to the result, not sure if it's worth doing? is it an issue to have the unused imports?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jackpope said this might be an issue so fixed here #30628

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