-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
…ecls ghstack-source-id: da8ae28a969cd027c076e36a9a815665a3af5754 Pull Request resolved: #31066
…n decls ghstack-source-id: c0a4b9548787365d7183a02ed26be88101a234cb Pull Request resolved: #31066
…n decls ghstack-source-id: 32e551e45a9d48085dd2a7ab3cc5efc112808900 Pull Request resolved: #31066
…n decls ghstack-source-id: 485a44f7203083d0bdff4eaf042e71d30a0a7897 Pull Request resolved: #31066
temporaries: actuallyEvaluatedTemporaries, | ||
knownImmutableIdentifiers, | ||
hoistableFromOptionals, | ||
registry, | ||
); | ||
nestedFnImmutableContext, |
There was a problem hiding this comment.
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
const maybeNonNull = getMaybeNonNullInInstruction(instr.value, context); | ||
if ( | ||
maybeNonNull != null && | ||
isImmutableAtInstr(maybeNonNull.fullPath.identifier, instr.id, context) | ||
) { | ||
assumedNonNullObjects.add(maybeNonNull); | ||
} |
There was a problem hiding this comment.
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")
if ( | ||
instr.value.kind === 'FunctionExpression' && | ||
!fn.env.config.enableTreatFunctionDepsAsConditional | ||
) { |
There was a problem hiding this comment.
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
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
)
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 { |
There was a problem hiding this comment.
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
)
…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)
…rtyLoads from function decls
d57086e
to
e0701a3
Compare
Extends #31066 to ObjectMethods (somehow missed this before).
`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
Extends #31066 to ObjectMethods (somehow missed this before).
Extends #31066 to ObjectMethods (somehow missed this before). -
`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 -
Extends #31066 to ObjectMethods (somehow missed this before). '
`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 '
Extends #31066 to ObjectMethods (somehow missed this before). '
`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 '
Extends #31066 to ObjectMethods (somehow missed this before). '
`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 '
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
`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
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)