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][hir] Only hoist always-accessed PropertyLoads from function decls #31066

Merged
merged 1 commit into from
Oct 3, 2024

Conversation

mofeiZ
Copy link
Contributor

@mofeiZ mofeiZ commented Sep 25, 2024

Stack from ghstack (oldest at bottom):

Prior to this PR, we consider all of a nested function's accessed paths as 'hoistable' (to the basic block in which the function was defined). Now, we traverse nested functions and find all paths hoistable to their entry block.

Note that this only replaces the hoisting part of function declarations, not dependencies. This realistically only affects optional chains within functions, which always get truncated to its inner non-optional path (see todo-infer-function-uncond-optionals-hoisted.tsx)

See newly added test fixtures for details

Update: Note that toggling enableTreatFunctionDepsAsConditional makes a non-trivial impact on granularity of inferred deps (i.e. we find that function declarations uniquely identify some paths as hoistable). Snapshot comparison of internal code shows ~2.5% of files get worse dependencies (internal link)

Copy link

vercel bot commented Sep 25, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-compiler-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 3, 2024 6:33pm

mofeiZ added a commit that referenced this pull request Sep 25, 2024
…ecls

ghstack-source-id: da8ae28a969cd027c076e36a9a815665a3af5754
Pull Request resolved: #31066
@facebook-github-bot facebook-github-bot added the React Core Team Opened by a member of the React Core Team label Sep 25, 2024
mofeiZ added a commit that referenced this pull request Sep 26, 2024
…n decls

ghstack-source-id: c0a4b9548787365d7183a02ed26be88101a234cb
Pull Request resolved: #31066
@mofeiZ mofeiZ changed the title [compiler][hir-rewrite] Stop hoisting PropertyLoads out of function decls [compiler][hir] Only hoist always-accessed PropertyLoads from function decls Sep 26, 2024
mofeiZ added a commit that referenced this pull request Sep 26, 2024
…n decls

ghstack-source-id: 32e551e45a9d48085dd2a7ab3cc5efc112808900
Pull Request resolved: #31066
mofeiZ added a commit that referenced this pull request Sep 30, 2024
…n decls

ghstack-source-id: 485a44f7203083d0bdff4eaf042e71d30a0a7897
Pull Request resolved: #31066
Comment on lines +115 to +119
temporaries: actuallyEvaluatedTemporaries,
knownImmutableIdentifiers,
hoistableFromOptionals,
registry,
);
nestedFnImmutableContext,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that we could technically reuse knownImmutableIdentifiers for non-mutable captured identifiers. This is a bit more explicit

Comment on lines +348 to +349
const maybeNonNull = getMaybeNonNullInInstruction(instr.value, context);
if (
maybeNonNull != null &&
isImmutableAtInstr(maybeNonNull.fullPath.identifier, instr.id, context)
) {
assumedNonNullObjects.add(maybeNonNull);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just code movement. All mutability check logic moved to isImmutableAtInstr because it got more complex ("if we're within a nested function, check if the base identifier is in nestedFnImmutableContext, otherwise do the scope / mutable range check")

Comment on lines +355 to +353
if (
instr.value.kind === 'FunctionExpression' &&
!fn.env.config.enableTreatFunctionDepsAsConditional
) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the core logic for collecting hoistable paths in inner functions

Comment on lines +41 to +55
if ($[0] !== shouldReadA || $[1] !== a.b.c) {
t1 = (
<Stringify
fn={() => {
if (shouldReadA) {
return a.b.c;
}
return null;
}}
shouldInvokeFns={true}
/>
);
$[0] = shouldReadA;
$[1] = a.b.c;
$[2] = t1;
Copy link
Contributor Author

@mofeiZ mofeiZ Oct 1, 2024

Choose a reason for hiding this comment

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

Note that the propagate-hir-fork version of this fixture (correctly) only adds a as a dependency

local = $[1];
}
let t1;
if ($[2] !== shouldReadA || $[3] !== local) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

conditional dependency within a function is truncated down to non-null load (e.g. local)

Comment on lines +44 to +59
if ($[0] !== shouldReadA || $[1] !== a) {
t1 = (
<Stringify
fn={() => {
if (shouldReadA) {
return a.b.c;
}
return null;
}}
shouldInvokeFns={true}
/>
);
$[0] = shouldReadA;
$[1] = a;
$[2] = t1;
} else {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

conditional dependency within a function is truncated down to non-null load (e.g. a)

mofeiZ added a commit that referenced this pull request Oct 3, 2024
…ting (#31032)

Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at
bottom):
* #31066
* __->__ #31032

Prior to this PR, we check whether the property load source (e.g. the
evaluation of `<base>` in `<base>.property`) is mutable + scoped to
determine whether the property load itself is eligible for hoisting.
This changes to check the base identifier of the load.
- This is needed for the next PR #31066. We want to evaluate whether the
base identifier is mutable within the context of the *outermost
function*. This is because all LoadLocals and PropertyLoads within a
nested function declaration have mutable-ranges within the context of
the function, but the base identifier is a context variable.
- A side effect is that we no longer infer loads from props / other
function arguments as mutable in edge cases (e.g. props escaping out of
try-blocks or being assigned to context variables)
@mofeiZ mofeiZ changed the base branch from gh/mofeiZ/23/base to main October 3, 2024 17:56
@mofeiZ mofeiZ changed the base branch from main to gh/mofeiZ/23/base October 3, 2024 18:26
@mofeiZ mofeiZ force-pushed the gh/mofeiZ/23/head branch from d57086e to e0701a3 Compare October 3, 2024 18:32
@mofeiZ mofeiZ changed the base branch from gh/mofeiZ/23/base to main October 3, 2024 18:32
@mofeiZ mofeiZ merged commit 1460d67 into main Oct 3, 2024
5 checks passed
mofeiZ added a commit that referenced this pull request Oct 24, 2024
Extends #31066 to ObjectMethods (somehow missed this before).
mofeiZ added a commit that referenced this pull request Oct 24, 2024
`PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths

This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions
mofeiZ added a commit that referenced this pull request Oct 24, 2024
Extends #31066 to ObjectMethods (somehow missed this before).
mofeiZ added a commit that referenced this pull request Oct 24, 2024
Extends #31066 to ObjectMethods (somehow missed this before).

-
mofeiZ added a commit that referenced this pull request Oct 24, 2024
`PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths

This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions
-
mofeiZ added a commit that referenced this pull request Oct 24, 2024
Extends #31066 to ObjectMethods (somehow missed this before).

'
mofeiZ added a commit that referenced this pull request Oct 24, 2024
`PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths

This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions

'
mofeiZ added a commit that referenced this pull request Oct 24, 2024
Extends #31066 to ObjectMethods (somehow missed this before).

'
mofeiZ added a commit that referenced this pull request Oct 24, 2024
`PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths

This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions

'
mofeiZ added a commit that referenced this pull request Nov 5, 2024
Extends #31066 to ObjectMethods (somehow missed this before).

'
mofeiZ added a commit that referenced this pull request Nov 5, 2024
`PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths

This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions

'
mofeiZ added a commit that referenced this pull request Nov 5, 2024
Extends #31066 to ObjectMethods (somehow missed this before).

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31197).
* #31204
* #31202
* #31203
* #31201
* #31200
* #31346
* #31199
* #31431
* #31345
* __->__ #31197
mofeiZ added a commit that referenced this pull request Nov 5, 2024
`PropertyPathRegistry` is responsible for uniqueing identifier and
property paths. This is necessary for the hoistability CFG merging logic
which takes unions and intersections of these nodes to determine a basic
block's hoistable reads, as a function of its neighbors. We also depend
on this to merge optional chained and non-optional chained property
paths

This fixes a small bug in #31066 in which we create a new registry for
nested functions. Now, we use the same registry for a component / hook
and all its inner functions

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31345).
* #31204
* #31202
* #31203
* #31201
* #31200
* #31346
* #31199
* #31431
* __->__ #31345
* #31197
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants