-
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] Factor out function effects from reference effects #30920
Conversation
Summary: This PR performs a major refactor of InferReferenceEffects to separate out the work on marking places with Effects from inferring FunctionEffects. The behavior should be identical after this change (see [internal sync](https://www.internalfb.com/intern/everpaste/?handle=GN74VxscnUaztTYDAL8q0CRWBIxibsIXAAAB)) but the FunctionEffect logic should be easier to work with. These analyses are unfortunately still deeply linked--the FunctionEffect analysis needs to reason about the "current" value kind for each point in the program, while the InferReferenceEffects algorithm performs global updates on the state of the program (e.g. freezing). In the future, it might be possible to make these entirely separate passes if we store the ValueKind directly on places. For the most part, the logic of reference effects and function effects can be cleanly separated: for each instruction and terminal, we visit its places and infer their effects, and then we visit its places and infer any function effects that they cause. The biggest wrinkle here is that when a transitive function freeze operation occurs, it has to happen *after* inferring the function effects on the place, because otherwise we may convert a value from Context to Frozen, which will cause the ContextualMutation function effect to be converted to a ReactMutation effect too early. This can be observed in a case like this: ``` export default component C() { foo(() => { const p = {}; return () => { p['a'] = 1 }; }); } ``` Here when the outer function returns the inner function, it freezes the inner function which transitively freezes `p`. But before that freeze happens, we need to replay the ContextualMutation on the inner function to determine that the value is mutable in the outer context. If we froze `p` first, we would instead convert the ContextualMutation to a ReactMutation and error. To handle this, InferReferenceEffects now delays the exection of the freezeValue action until after it's called the helper functions that generate function effects. So the order of operations on a given place is now set effect --> generate function effects --> transitively freeze dependencies, if applicable [ghstack-poisoned]
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Summary: This PR performs a major refactor of InferReferenceEffects to separate out the work on marking places with Effects from inferring FunctionEffects. The behavior should be identical after this change (see [internal sync](https://www.internalfb.com/intern/everpaste/?handle=GN74VxscnUaztTYDAL8q0CRWBIxibsIXAAAB)) but the FunctionEffect logic should be easier to work with. These analyses are unfortunately still deeply linked--the FunctionEffect analysis needs to reason about the "current" value kind for each point in the program, while the InferReferenceEffects algorithm performs global updates on the state of the program (e.g. freezing). In the future, it might be possible to make these entirely separate passes if we store the ValueKind directly on places. For the most part, the logic of reference effects and function effects can be cleanly separated: for each instruction and terminal, we visit its places and infer their effects, and then we visit its places and infer any function effects that they cause. The biggest wrinkle here is that when a transitive function freeze operation occurs, it has to happen *after* inferring the function effects on the place, because otherwise we may convert a value from Context to Frozen, which will cause the ContextualMutation function effect to be converted to a ReactMutation effect too early. This can be observed in a case like this: ``` export default component C() { foo(() => { const p = {}; return () => { p['a'] = 1 }; }); } ``` Here when the outer function returns the inner function, it freezes the inner function which transitively freezes `p`. But before that freeze happens, we need to replay the ContextualMutation on the inner function to determine that the value is mutable in the outer context. If we froze `p` first, we would instead convert the ContextualMutation to a ReactMutation and error. To handle this, InferReferenceEffects now delays the exection of the freezeValue action until after it's called the helper functions that generate function effects. So the order of operations on a given place is now set effect --> generate function effects --> transitively freeze dependencies, if applicable ghstack-source-id: 21cb50c14054e7e7a307acb595ef30b54c2f2a52 Pull Request resolved: #30920
let valueKind: AbstractValue; | ||
const defaultLvalueEffect = Effect.ConditionallyMutate; | ||
let continuation: Continuation; | ||
const freezeActions: Array<FreezeAction> = []; |
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.
As a side refactoring in this PR, I found the structure of the main IRE switch to be kind of confusing -- each case ending with continue
if it's already performed all the actions it needs to, or with break
if the case sets up the effect
and valueKind
variables to be handled in the fallthrough. This changes it to be explicitly controlled by a continuation
variable (kind of a misnomer since it's not an actual function continuation, but *shrug*). All cases now break.
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 looks great. Per discussion i think we should just save the ValueKind on Place in InferReferenceEffects so that we can more cleanly separate these passes, that idea also came up in the context of @mofeiZ's work on PropagateScopeDepsHIR
Summary: This PR performs a major refactor of InferReferenceEffects to separate out the work on marking places with Effects from inferring FunctionEffects. The behavior should be identical after this change (see [internal sync](https://www.internalfb.com/intern/everpaste/?handle=GN74VxscnUaztTYDAL8q0CRWBIxibsIXAAAB)) but the FunctionEffect logic should be easier to work with. These analyses are unfortunately still deeply linked--the FunctionEffect analysis needs to reason about the "current" value kind for each point in the program, while the InferReferenceEffects algorithm performs global updates on the state of the program (e.g. freezing). In the future, it might be possible to make these entirely separate passes if we store the ValueKind directly on places. For the most part, the logic of reference effects and function effects can be cleanly separated: for each instruction and terminal, we visit its places and infer their effects, and then we visit its places and infer any function effects that they cause. The biggest wrinkle here is that when a transitive function freeze operation occurs, it has to happen *after* inferring the function effects on the place, because otherwise we may convert a value from Context to Frozen, which will cause the ContextualMutation function effect to be converted to a ReactMutation effect too early. This can be observed in a case like this: ``` export default component C() { foo(() => { const p = {}; return () => { p['a'] = 1 }; }); } ``` Here when the outer function returns the inner function, it freezes the inner function which transitively freezes `p`. But before that freeze happens, we need to replay the ContextualMutation on the inner function to determine that the value is mutable in the outer context. If we froze `p` first, we would instead convert the ContextualMutation to a ReactMutation and error. To handle this, InferReferenceEffects now delays the exection of the freezeValue action until after it's called the helper functions that generate function effects. So the order of operations on a given place is now set effect --> generate function effects --> transitively freeze dependencies, if applicable ghstack-source-id: 21cb50c14054e7e7a307acb595ef30b54c2f2a52 Pull Request resolved: #30920
**breaking change for canary users: Bumps peer dependency of React from `19.0.0-rc-206df66e-20240912` to `19.0.0-rc-a99d8e8d-20240916`** [diff facebook/react@206df66e...a99d8e8d](facebook/react@206df66...a99d8e8) <details> <summary>React upstream changes</summary> - facebook/react#30977 - facebook/react#30971 - facebook/react#30922 - facebook/react#30917 - facebook/react#30902 - facebook/react#30912 - facebook/react#30970 - facebook/react#30969 - facebook/react#30967 - facebook/react#30966 - facebook/react#30960 - facebook/react#30968 - facebook/react#30961 - facebook/react#28255 - facebook/react#30957 - facebook/react#30958 - facebook/react#30959 - facebook/react#30951 - facebook/react#30954 - facebook/react#30920 - facebook/react#30942 </details>
Stack from ghstack (oldest at bottom):
Summary:
This PR performs a major refactor of InferReferenceEffects to separate out the work on marking places with Effects from inferring FunctionEffects. The behavior should be identical after this change (see internal sync) but the FunctionEffect logic should be easier to work with.
These analyses are unfortunately still deeply linked--the FunctionEffect analysis needs to reason about the "current" value kind for each point in the program, while the InferReferenceEffects algorithm performs global updates on the state of the program (e.g. freezing). In the future, it might be possible to make these entirely separate passes if we store the ValueKind directly on places.
For the most part, the logic of reference effects and function effects can be cleanly separated: for each instruction and terminal, we visit its places and infer their effects, and then we visit its places and infer any function effects that they cause. The biggest wrinkle here is that when a transitive function freeze operation occurs, it has to happen after inferring the function effects on the place, because otherwise we may convert a value from Context to Frozen, which will cause the ContextualMutation function effect to be converted to a ReactMutation effect too early. This can be observed in a case like this:
Here when the outer function returns the inner function, it freezes the inner function which transitively freezes
p
. But before that freeze happens, we need to replay the ContextualMutation on the inner function to determine that the value is mutable in the outer context. If we frozep
first, we would instead convert the ContextualMutation to a ReactMutation and error.To handle this, InferReferenceEffects now delays the exection of the freezeValue action until after it's called the helper functions that generate function effects. So the order of operations on a given place is now
set effect --> generate function effects --> transitively freeze dependencies, if applicable