-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[AddressLowering] Project at storage. #63033
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
[AddressLowering] Project at storage. #63033
Conversation
@swift-ci please test |
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.
Great!
Let's be clear though that the broader intention is not for SIL to reuse address projections across blocks. I think that's a bad idea and hides bugs.
If you can get sinkAddressProjection to work, that would be great. Otherwise, may be you can add a TODO to document the intention.
f9e9853
to
08db6f6
Compare
@atrick Added projection sinking using a leaf-first walk over the projections and a new function |
08db6f6
to
5339191
Compare
@atrick As you said, it is sufficient to iterate |
5339191
to
9659472
Compare
@swift-ci please test |
@atrick Restored the |
@swift-ci please test windows platform |
@@ -1290,6 +1306,9 @@ SILValue AddressLoweringState::getEarliestStoragePoint(SILValue userValue, | |||
// not allowed. The projection will be inserted here. | |||
return SILValue(); | |||
} | |||
if (auto *loi = getLatestOpeningInst(pair)) { | |||
return cast<SingleValueInstruction>(loi); |
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.
Why do we only consider the first latest opening instruction? Can we way say something about the relationship between open archetypes based on the nature of use projections?
If we find a latest opening instruction, then why don't we also check dominance on the non-projection storage address the same way we do for the opening instruction?
Can we just lump all dependencies together and check dominance for each without caring about their relationship?
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.
Why do we only consider the first latest opening instruction?
This was a false assumption I was making. I added a test case demonstrating that. It failed when considering only the first such instruction.
Can we way say something about the relationship between open archetypes based on the nature of use projections?
We can say that they all dominate or are dominated by one another thanks to them all dominating the largest aggregate. But we can't say anything about their relative ordering based on when they start appearing in the projection tower.
If we find a latest opening instruction, then why don't we also check dominance on the non-projection storage address the same way we do for the opening instruction?
Fixed.
Can we just lump all dependencies together and check dominance for each without caring about their relationship?
Yep, done.
/// As a result of the above consideration, when this function is called for a | ||
/// phi, it returns a null value if the storage involves either sort of | ||
/// effectful initialization. | ||
SILValue AddressLoweringState::getEarliestStoragePoint(SILValue userValue, |
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.
Can we be explicit about the relationship between this function and getProjectionInsertionPoint which is called later. Like, if this returns a valid address, the getProjectionInsertionPoint must be guaranteed to find a dominating address, so all dependencies discovered here need to be rediscovered there.
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.
Fixed.
// It's only necessary to consider the obstruction to the earliest | ||
// projection because obstructions to any others must dominate it. | ||
if (auto *loi = pass.getLatestOpeningInst(pair)) | ||
obstruction = loi->getNextInstruction(); |
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.
Same question about obstructions: Why do we only consider the first latest opening instruction?
But this time we need to consider def projections. (Maybe that doesn't matter.)
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.
Fixed to consider all obstructions. Instead of collecting them into a list, it relies on the fact that the opening instructions all must either dominate or be dominated by one another.
It's a general purpose helper and there will soon be another user.
Per Andrew Trick, its author.
That there was a default wasn't used anywhere.
Describe what all chains look like by analyzing the four theoretically possible kinds of links--only three of the four are actually possible.
Of existing function getProjectedStorage.
Used the iterator to replace recursion in get*Storage functions with for loops.
Promoted the functionality from createStackAllocation to a named member AddressLoweringState::getLatestOpeningInst so that it can be employed elsewhere.
Moved the assert that the value which opens an archetype exists before the call to get its defining instruction which would be an NPE if the value were in fact null.
9659472
to
d269358
Compare
@swift-ci please test |
Please test with following pull request: @swift-ci please test macos platform |
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.
LGTM
assert(isa<SILFunctionArgument>(baseStorage->storageAddress)); | ||
// Recurse through all storage projections to find the point where the | ||
// storage has been allocated and any opening instructions involved in | ||
// any of those projections' types. |
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 all good.
But it may be more clear if we also outright say something like:
The base storage address and all of the opened types used by the projections must dominate 'incomingValues' because we need to materialize corresponding address projections at those points.
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.
Done.
The pass rewrites opaque values in reverse post order of their definitions. When an opaque value is rewritten, both its definition and its uses are rewritten. When a def or a use is rewritten, in order to materialize an address with which to rewrite, projection instructions are created. Previously, these projections were created at the site of the def or use. Some of these projection instructions may be reused when rewriting later opaque values. As a result, it's possible to have two opaque values `A` and `B` (which are rewritten in that order) such that rewriting a use of `A` which occurs "after" the def (or a use) of `B` creates a projection `P` which is then used by that "earlier" def (or use) of `B`: ``` A = B = // relies on P use B use A // creates some projection P ``` When rewriting the def (or that use, respectively) of `B`, the projection which was created for the use of `A` will be reused. And previously, the projection would be created at the use of A. But that projection instruction was "after" the place where it is used when rewriting the def or use of `B`. That's invalid! To address this, rather than creating projections at the instruction being rewritten, instead create them "as early as possible". The locations where they will be created are chosen so as to dominate all uses.
Projections may involve opened archetypes. They must be dominated by the instructions which open those archetypes. When determining the earliest storage point for a value and the point at which to insert its projections, look for such obstructions in the projection chain. The first one found is the earliest storage point.
Visit the tree of projections in post-order, sinking each to the lowest position in the least common ancestor of all uses.
d269358
to
a27b390
Compare
@swift-ci please smoke test and merge |
The pass rewrites opaque values in reverse post order of their definitions. When an opaque value is rewritten, both its definition and its uses are rewritten. When a def or a use is rewritten, in order to materialize an address with which to rewrite, projection instructions are created. Previously, these projections were created at the site of the def or use. Some of these projection instructions may be reused when rewriting later opaque values.
As a result, it's possible to have two opaque values
A
andB
(which are rewritten in that order) such that rewriting a use ofA
which occurs "after" the def (or a use) ofB
creates a projectionP
which is then used by that "earlier" def (or use) ofB
:When rewriting the def (or that use, respectively) of
B
, the projection which was created for the use ofA
will be reused. And previously, the projection would be created at the use of A. But that projection instruction was "after" the place where it is used when rewriting the def or use ofB
. That's invalid!To address this, rather than creating projections at the instruction being rewritten, instead create them "as early as possible". The locations where they will be created are chosen so as to dominate all uses.