-
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] Promote temporaries when necessary to prevent codegen reordering over side-effectful operations #30554
Conversation
…dering over side-effectful operations [ghstack-poisoned]
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
…dering over side-effectful operations ghstack-source-id: 3f0e3b6c9d66c592c0f95806bf95410f22b10e68 Pull Request resolved: #30554
…odegen reordering over side-effectful operations" [ghstack-poisoned]
…dering over side-effectful operations ghstack-source-id: f9a0f2337bae11781e4e12448784816b49ebd652 Pull Request resolved: #30554
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 overall makes sense, just a few questions/comments. I'd love eyes on it from @mofeiZ as well.
/* | ||
* Unpromoted temporaries will be emitted at their use sites rather than as separate | ||
* declarations. However, this causes errors if an interposing temporary has been | ||
* promoted, or if an interposing instruction has had its lvalues |
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.
looks like text got cut off here?
state.set(instruction.lvalue.identifier.id, [ | ||
instruction.lvalue.identifier, | ||
false, | ||
]); |
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.
seems like you could avoid creating preEntries
(which will addd up since we create it for every such instruction) by deferring the set here until after you've iterated state
/* | ||
* Visit if branches separately -- we don't want | ||
* to promote a use in the alternate branch because | ||
* of an interposing instruction in the consequent branch, | ||
* because it's not actually interposing at runtime. | ||
* After finishing the branches, merge their states. | ||
*/ |
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 makes sense, you want to merge the state for the outer block context, but don't want to merge the state of the locals per branch.
can you add some more fixtures to stress test this?
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.
It turns out I don't think I can construct an example that exercises this behavior--this would require a temporary to be defined before an if terminal and then used within one branch of the terminal.
I'm inclined to drop this code path because of this--even if I'm wrong and we can construct an example that exercises this, the only consequence would be an unnecessarily promoted temporary.
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.
Makes sense!
…odegen reordering over side-effectful operations" This PR addresses the issue recorded [here](https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-codegen-inline-iife.ts), where codegen reorders instructions by moving a temporary across a promoted temporary (or an instruction whose lvalue has been pruned) in an unsafe way. We fix this by adding a new sub-pass to PromoteUsedTemporaries which looks for cases where the definition and the use of an unpromoted temporary, whose value could potentially be mutated, have an instruction in between them that 1. can cause mutation and 2. will be emitted as a separate statement. For our purposes, 1. means function calls and stores, and 2. means either that we've pruned the lvalue or that we've promoted the identifier (or technically that the identifier was named in the first place, but I don't think we could be reodering across it in the first place for this case). If we find such a temporary with an interposing mutative instruction, we'll promote it to a temporary as well. [ghstack-poisoned]
…dering over side-effectful operations ghstack-source-id: 0907f8950bce5e241aa65c7691749914add70905 Pull Request resolved: #30554
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.
Logic overall makes sense to me, pretty clever! Is the underlying intuition here that instructions are emitted in the correct order (e.g. IR order which follows source-code evaluation semantics) except for cases in which Forget adds statements, which effectively hoists the statement / rvals used by the statement to be emitted out of order?
I had a bit of trouble following the this.#consts
tracking and constStore, but I think I now understand why this is needed (writing down some notes to check my understanding).
Checking constStore
when handling non-reorderable instructions makes sense -- const stores are reorderable, but reassignments are not (overall logic seems similar to memory barriers).
[0]: $0 = ... // preEntry = [$0] (rvals evaluated before instruction [1])
[1]: ... // <-- non-reorderable instruction that gets codegenned to
// a statement / expressionStatement (e.g. CallExpression)
[2]: <use $0> // later reference to rval should promote $0 to a named temporary
this.#consts
track const-bindings to allow reordering of any loads from a const-binding decl, e.g.
[0]: StoreLocal const x$0 = ...
[1]: $1 = LoadLocal x$0
[2]: <non-reorderable / fence instruction>
[3]: <use $1> // No need to promote $1 since x$0 is a constant binding!
It seems that the fixture results are pretty stable (no false positives!) -- I'd be curious whether we observe any snapshot differences when syncing internally. I've found syncs to be a pretty great stress test for these types of changes |
@mofeiZ tested this internally (P1510568787) and found that this promotes a lot of global loads, like global.prop in [this example(https://playground.react.dev/#N4Igzg9grgTgxgUxALhAMygOzgFwJYSYAEUYCAKgmDgBR6YAOAlEcADrFEwI6zEA8AQyIMYEBgF5gAcwA2EAEaDZAOlHiAvkQg4AFghhS0dRky0B6AHwcNIDUA). This is technically correct--what if the function call has an alias to |
…odegen reordering over side-effectful operations" This PR addresses the issue recorded [here](https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-codegen-inline-iife.ts), where codegen reorders instructions by moving a temporary across a promoted temporary (or an instruction whose lvalue has been pruned) in an unsafe way. We fix this by adding a new sub-pass to PromoteUsedTemporaries which looks for cases where the definition and the use of an unpromoted temporary, whose value could potentially be mutated, have an instruction in between them that 1. can cause mutation and 2. will be emitted as a separate statement. For our purposes, 1. means function calls and stores, and 2. means either that we've pruned the lvalue or that we've promoted the identifier (or technically that the identifier was named in the first place, but I don't think we could be reodering across it in the first place for this case). If we find such a temporary with an interposing mutative instruction, we'll promote it to a temporary as well. [ghstack-poisoned]
…dering over side-effectful operations ghstack-source-id: 01c37b2e68b235c0cb9aada2f7062919d0d873db Pull Request resolved: #30554
agreed, let’s relax the promotion requirements. We already freely reorder global accesses in ConstantPropagation. |
…odegen reordering over side-effectful operations" This PR addresses the issue recorded [here](https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-codegen-inline-iife.ts), where codegen reorders instructions by moving a temporary across a promoted temporary (or an instruction whose lvalue has been pruned) in an unsafe way. We fix this by adding a new sub-pass to PromoteUsedTemporaries which looks for cases where the definition and the use of an unpromoted temporary, whose value could potentially be mutated, have an instruction in between them that 1. can cause mutation and 2. will be emitted as a separate statement. For our purposes, 1. means function calls and stores, and 2. means either that we've pruned the lvalue or that we've promoted the identifier (or technically that the identifier was named in the first place, but I don't think we could be reodering across it in the first place for this case). If we find such a temporary with an interposing mutative instruction, we'll promote it to a temporary as well. [ghstack-poisoned]
…dering over side-effectful operations ghstack-source-id: 639191e63a0d2b4290d1265a2da12fb17de750d9 Pull Request resolved: #30554
Stack from ghstack (oldest at bottom):
This PR addresses the issue recorded here, where codegen reorders instructions by moving a temporary across a promoted temporary (or an instruction whose lvalue has been pruned) in an unsafe way.
We fix this by adding a new sub-pass to PromoteUsedTemporaries which looks for cases where the definition and the use of an unpromoted temporary, whose value could potentially be mutated, have an instruction in between them that 1. can cause mutation and 2. will be emitted as a separate statement. For our purposes, 1. means function calls and stores, and 2. means either that we've pruned the lvalue or that we've promoted the identifier (or technically that the identifier was named in the first place, but I don't think we could be reodering across it in the first place for this case). If we find such a temporary with an interposing mutative instruction, we'll promote it to a temporary as well.