-
Notifications
You must be signed in to change notification settings - Fork 48.6k
[compiler] Stop using function dependencies
in propagateScopeDeps
#31200
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -440,14 +440,6 @@ class Context { | |
|
||
// Checks if identifier is a valid dependency in the current scope | ||
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { | ||
// ref.current access is not a valid dep | ||
if ( | ||
isUseRefType(maybeDependency.identifier) && | ||
maybeDependency.path.at(0)?.property === 'current' | ||
) { | ||
return false; | ||
} | ||
|
||
// ref value is not a valid dep | ||
if (isRefValueType(maybeDependency.identifier)) { | ||
return false; | ||
|
@@ -549,6 +541,16 @@ class Context { | |
}); | ||
} | ||
|
||
// ref.current access is not a valid dep | ||
if ( | ||
isUseRefType(maybeDependency.identifier) && | ||
maybeDependency.path.at(0)?.property === 'current' | ||
) { | ||
maybeDependency = { | ||
identifier: maybeDependency.identifier, | ||
path: [], | ||
}; | ||
} | ||
if (this.#checkValidDependency(maybeDependency)) { | ||
this.#dependencies.value!.push(maybeDependency); | ||
} | ||
|
@@ -661,35 +663,54 @@ function collectDependencies( | |
|
||
const scopeTraversal = new ScopeBlockTraversal(); | ||
|
||
for (const [blockId, block] of fn.body.blocks) { | ||
scopeTraversal.recordScopes(block); | ||
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); | ||
if (scopeBlockInfo?.kind === 'begin') { | ||
context.enterScope(scopeBlockInfo.scope); | ||
} else if (scopeBlockInfo?.kind === 'end') { | ||
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); | ||
} | ||
|
||
// Record referenced optional chains in phis | ||
for (const phi of block.phis) { | ||
for (const operand of phi.operands) { | ||
const maybeOptionalChain = temporaries.get(operand[1].identifier.id); | ||
if (maybeOptionalChain) { | ||
context.visitDependency(maybeOptionalChain); | ||
const handleFunction = (fn: HIRFunction): void => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change is code movement (moving logic into |
||
for (const [blockId, block] of fn.body.blocks) { | ||
scopeTraversal.recordScopes(block); | ||
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); | ||
if (scopeBlockInfo?.kind === 'begin') { | ||
context.enterScope(scopeBlockInfo.scope); | ||
} else if (scopeBlockInfo?.kind === 'end') { | ||
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); | ||
} | ||
// Record referenced optional chains in phis | ||
for (const phi of block.phis) { | ||
for (const operand of phi.operands) { | ||
const maybeOptionalChain = temporaries.get(operand[1].identifier.id); | ||
if (maybeOptionalChain) { | ||
context.visitDependency(maybeOptionalChain); | ||
} | ||
} | ||
} | ||
} | ||
for (const instr of block.instructions) { | ||
if (!processedInstrsInOptional.has(instr)) { | ||
handleInstruction(instr, context); | ||
for (const instr of block.instructions) { | ||
if ( | ||
fn.env.config.enableFunctionDependencyRewrite && | ||
(instr.value.kind === 'FunctionExpression' || | ||
instr.value.kind === 'ObjectMethod') | ||
) { | ||
context.declare(instr.lvalue.identifier, { | ||
id: instr.id, | ||
scope: context.currentScope, | ||
}); | ||
/** | ||
* Recursively visit the inner function to extract dependencies there | ||
*/ | ||
const wasInInnerFn = context.inInnerFn; | ||
context.inInnerFn = true; | ||
handleFunction(instr.value.loweredFunc.func); | ||
context.inInnerFn = wasInInnerFn; | ||
Comment on lines
+698
to
+700
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Recursive call. See explanation in |
||
} else if (!processedInstrsInOptional.has(instr)) { | ||
handleInstruction(instr, context); | ||
} | ||
} | ||
} | ||
|
||
if (!processedInstrsInOptional.has(block.terminal)) { | ||
for (const place of eachTerminalOperand(block.terminal)) { | ||
context.visitOperand(place); | ||
if (!processedInstrsInOptional.has(block.terminal)) { | ||
for (const place of eachTerminalOperand(block.terminal)) { | ||
context.visitOperand(place); | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
|
||
handleFunction(fn); | ||
return context.deps; | ||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { | |
```javascript | ||
import { c as _c } from "react/compiler-runtime"; | ||
function component(a, b) { | ||
const $ = _c(5); | ||
let t0; | ||
if ($[0] !== b) { | ||
t0 = { b }; | ||
$[0] = b; | ||
$[1] = t0; | ||
} else { | ||
t0 = $[1]; | ||
} | ||
const y = t0; | ||
const $ = _c(2); | ||
const y = { b }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
let z; | ||
if ($[2] !== a || $[3] !== y) { | ||
if ($[0] !== a) { | ||
z = { a }; | ||
const x = function () { | ||
z.a = 2; | ||
}; | ||
|
||
x(); | ||
$[2] = a; | ||
$[3] = y; | ||
$[4] = z; | ||
$[0] = a; | ||
$[1] = z; | ||
} else { | ||
z = $[4]; | ||
z = $[1]; | ||
} | ||
return z; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
|
||
## Input | ||
|
||
```javascript | ||
// @validatePreserveExistingMemoizationGuarantees | ||
import {useCallback} from 'react'; | ||
import {Stringify} from 'shared-runtime'; | ||
|
||
/** | ||
* 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. | ||
*/ | ||
function Foo(props) { | ||
let contextVar; | ||
if (props.cond) { | ||
contextVar = {val: 2}; | ||
} else { | ||
contextVar = {}; | ||
} | ||
|
||
const cb = useCallback(() => [contextVar.val], [contextVar.val]); | ||
|
||
return <Stringify cb={cb} shouldInvokeFns={true} />; | ||
} | ||
|
||
export const FIXTURE_ENTRYPOINT = { | ||
fn: Foo, | ||
params: [{cond: true}], | ||
}; | ||
|
||
``` | ||
|
||
|
||
## Error | ||
|
||
``` | ||
22 | } | ||
23 | | ||
> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); | ||
| ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) | ||
25 | | ||
26 | return <Stringify cb={cb} shouldInvokeFns={true} />; | ||
27 | } | ||
``` | ||
|
||
|
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 this is deleted in #31204