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] Stop using function dependencies in propagateScopeDeps #31200

Merged
merged 2 commits into from
Nov 15, 2024
Merged

Conversation

mofeiZ
Copy link
Contributor

@mofeiZ mofeiZ commented Oct 11, 2024

Recursively visit inner function instructions to extract dependencies instead of using LoweredFunction.dependencies directly.

This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete LoweredFunction.dependencies altogether (#31204).

Some nice side effects


Stack created with Sapling. Best reviewed with ReviewStack.

Copy link

vercel bot commented Oct 11, 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 Nov 15, 2024 5:06pm

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Oct 11, 2024
@mofeiZ mofeiZ changed the title [compiler][needs cleanup]: stop using function deps in PropagateScopeDepsHIR [compiler] Stop using function dependencies in propagateScopeDeps Oct 24, 2024
@mofeiZ mofeiZ force-pushed the pr31200 branch 2 times, most recently from a84a59d to c5b7421 Compare October 24, 2024 18:13
@mofeiZ mofeiZ marked this pull request as ready for review October 24, 2024 19:19
mofeiZ added a commit that referenced this pull request Oct 24, 2024
Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200

Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions.
1. collectTemporariesSidemap
2. collectOptionalChainSidemap
3. collectHoistablePropertyLoads
    - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions
4. collectDependencies

Now, we have
1. collectTemporariesSidemap
    - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions
2. collectOptionalChainSidemap
    - recursively records optional chain sidemaps in inner functions
3. collectHoistablePropertyLoads
    - (unchanged, except to remove recursive collection of temporaries)
4. collectDependencies
    - unchanged: to be modified to recursively collect dependencies in next PR

'
@@ -230,6 +230,8 @@ const EnvironmentConfigSchema = z.object({
*/
enableUseTypeAnnotations: z.boolean().default(false),

enableFunctionDependencyRewrite: z.boolean().default(true),
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 this is deleted in #31204

const maybeOptionalChain = temporaries.get(operand[1].identifier.id);
if (maybeOptionalChain) {
context.visitDependency(maybeOptionalChain);
const handleFunction = (fn: HIRFunction): void => {
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 change is code movement (moving logic into handleFunction) so that we can recursively visit nested functions

Comment on lines +705 to +700
context.inInnerFn = true;
handleFunction(instr.value.loweredFunc.func);
context.inInnerFn = wasInInnerFn;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Recursive call. See explanation in Context.#declare for why inInnerFn is needed

}
const y = t0;
const $ = _c(2);
const y = { b };
Copy link
Contributor Author

Choose a reason for hiding this comment

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

const y = { b } is dce'd in #31204 (after we delete LoweredFunction.dependencies)

mofeiZ added a commit that referenced this pull request Nov 5, 2024
Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200

Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions.
1. collectTemporariesSidemap
2. collectOptionalChainSidemap
3. collectHoistablePropertyLoads
    - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions
4. collectDependencies

Now, we have
1. collectTemporariesSidemap
    - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions
2. collectOptionalChainSidemap
    - recursively records optional chain sidemaps in inner functions
3. collectHoistablePropertyLoads
    - (unchanged, except to remove recursive collection of temporaries)
4. collectDependencies
    - unchanged: to be modified to recursively collect dependencies in next PR

'
mofeiZ added a commit that referenced this pull request Nov 5, 2024
Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200

Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions.
1. collectTemporariesSidemap
2. collectOptionalChainSidemap
3. collectHoistablePropertyLoads
    - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions
4. collectDependencies

Now, we have
1. collectTemporariesSidemap
    - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions
2. collectOptionalChainSidemap
    - recursively records optional chain sidemaps in inner functions
3. collectHoistablePropertyLoads
    - (unchanged, except to remove recursive collection of temporaries)
4. collectDependencies
    - unchanged: to be modified to recursively collect dependencies in next PR

'
mofeiZ added a commit that referenced this pull request Nov 5, 2024
Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200

Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions.
1. collectTemporariesSidemap
2. collectOptionalChainSidemap
3. collectHoistablePropertyLoads
    - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions
4. collectDependencies

Now, we have
1. collectTemporariesSidemap
    - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions
2. collectOptionalChainSidemap
    - recursively records optional chain sidemaps in inner functions
3. collectHoistablePropertyLoads
    - (unchanged, except to remove recursive collection of temporaries)
4. collectDependencies
    - unchanged: to be modified to recursively collect dependencies in next PR

'
mofeiZ added a commit that referenced this pull request Nov 6, 2024
`enablePropagateScopeDepsHIR` is now used extensively in Meta. This has
been tested for over two weeks in our e2e tests and production.

The rest of this stack deletes `LoweredFunction.dependencies`, which the
non-hir version of `PropagateScopeDeps` depends on. To avoid a more
forked HIR (non-hir with dependencies and hir with no dependencies),
let's go ahead and clean up the non-hir version of
PropagateScopeDepsHIR.

Note that all fixture changes in this PR were previously reviewed when
they were copied to `propagate-scope-deps-hir-fork`. Will clean up /
merge these duplicate fixtures in a later PR

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31199).
* #31202
* #31203
* #31201
* #31200
* #31346
* __->__ #31199
mofeiZ added a commit that referenced this pull request Nov 6, 2024
…ns (#31346)

Recursively collect identifier / property loads and optional chains from
inner functions. This PR is in preparation for #31200

Previously, we only did this in `collectHoistablePropertyLoads` to
understand hoistable property loads from inner functions.
1. collectTemporariesSidemap
2. collectOptionalChainSidemap
3. collectHoistablePropertyLoads
- ^ this recursively calls `collectTemporariesSidemap`,
`collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on
inner functions
4. collectDependencies

Now, we have
1. collectTemporariesSidemap
- recursively record identifiers in inner functions. Note that we track
all temporaries in the same map as `IdentifierIds` are currently unique
across functions
2. collectOptionalChainSidemap
    - recursively records optional chain sidemaps in inner functions
3. collectHoistablePropertyLoads
    - (unchanged, except to remove recursive collection of temporaries)
4. collectDependencies
- unchanged: to be modified to recursively collect dependencies in next
PR

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31346).
* #31202
* #31203
* #31201
* #31200
* __->__ #31346
* #31199
Comment on lines +6 to +14
* TODO: we're currently bailing out because `contextVar` is a context variable
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
* we took as eligible dependencies.
*
* One solution is to simply record `LoadContext` identifiers into the
* temporaries sidemap when the instruction occurs *after* the context
* variable's mutable range.
Copy link
Contributor

Choose a reason for hiding this comment

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

just checking, but how often does this case occur internally? would be great to double-check by syncing, if you haven't already

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We only observe this ~10 times in our biggest internal project: see comparison diff for this PR

@@ -34,9 +34,9 @@ function useFoo(t0) {
const $ = _c(2);
const { a } = t0;
let t1;
if ($[0] !== a.b) {
if ($[0] !== a.b?.c.d?.e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nice

React.useContext(FooContext);
const ref = React.useRef();
const [x, setX] = React.useState(false);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
if ($[0] !== ref) {
Copy link
Contributor

Choose a reason for hiding this comment

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

hmmm we shouldn't be taking refs as deps, this seems off?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be fixed in #31521. Will rebase and update

Copy link
Contributor

@josephsavona josephsavona left a comment

Choose a reason for hiding this comment

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

Overall looks good but see fixtures - looks like this causes us to take refs as deps in some cases which isn't quite right

@mofeiZ
Copy link
Contributor Author

mofeiZ commented Nov 12, 2024

Overall looks good but see fixtures - looks like this causes us to take refs as deps in some cases which isn't quite right

Ohh good catch. Debugged further and it looks like the underlying issue is how we special case ref.current accesses in propagateScopeDeps. Added a fix in #31521 which should also fix this

Copy link
Contributor

@josephsavona josephsavona left a comment

Choose a reason for hiding this comment

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

nice nice

We were previously filtering out `ref.current` dependencies in propagateScopeDependencies:checkValidDependency`. This is incorrect.

Instead, we now always take a dependency on ref values (the outer box) as they may be reactive. Pruning is done in pruneNonReactiveDependencies.

This PR includes a small patch to `collectReactiveIdentifier`. Prior to this, we conservatively assumed that pruned scopes always produced reactive declarations. This assumption fixed a bug with non-reactivity, but some of these declarations are `useRef` calls. Now we have special handling for this case
```js
// This often produces a pruned scope
React.useRef(1);
```
Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly.

This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204).

Some nice side effects
- optional-chaining deps for inner functions
- full DCE and outlining for inner functions (see #31202)
- fewer extraneous instructions (see #31204)

-
mofeiZ added a commit that referenced this pull request Nov 15, 2024
We were previously filtering out `ref.current` dependencies in
propagateScopeDependencies:checkValidDependency`. This is incorrect.

Instead, we now always take a dependency on ref values (the outer box)
as they may be reactive. Pruning is done in
pruneNonReactiveDependencies.

This PR includes a small patch to `collectReactiveIdentifier`. Prior to
this, we conservatively assumed that pruned scopes always produced
reactive declarations. This assumption fixed a bug with non-reactivity,
but some of these declarations are `useRef` calls. Now we have special
handling for this case
```js
// This often produces a pruned scope
React.useRef(1);
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31521).
* #31202
* #31203
* #31201
* #31200
* __->__ #31521
@mofeiZ mofeiZ merged commit c09402a into main Nov 15, 2024
19 checks passed
mofeiZ added a commit that referenced this pull request Nov 15, 2024
`JSXMemberExpression` is currently the only instruction (that I know of)
that directly references identifier lvalues without a corresponding
`LoadLocal`.

This has some side effects:
- deadcode elimination and constant propagation now reach
JSXMemberExpressions
- we can delete `LoweredFunction.dependencies` without dangling
references (previously, the only reference to JSXMemberExpression
objects in HIR was in function dependencies)
- JSXMemberExpression now is consistent with all other instructions
(e.g. has a rvalue-producing LoadLocal)

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31201).
* #31202
* #31203
* __->__ #31201
* #31200
* #31521
mofeiZ added a commit that referenced this pull request Nov 15, 2024
Add more `FIXTURE_ENTRYPOINT`s

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31203).
* #31202
* __->__ #31203
* #31201
* #31200
* #31521
mofeiZ added a commit that referenced this pull request Nov 15, 2024
Now that we rely on function context exclusively, let's clean up
`HIRFunction.context` after DCE. This PR is in preparation of #31204,
which would otherwise have unnecessary declarations (of context values
that become entirely DCE'd)

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31202).
* __->__ #31202
* #31203
* #31201
* #31200
* #31521
@poteto poteto deleted the pr31200 branch November 18, 2024 15:18
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