-
Notifications
You must be signed in to change notification settings - Fork 10.5k
EscapeAnalysis: rework graph update and merge algorithms #28139
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
Conversation
@eeckstein This PR has the bulk of the underlying changes to EscapeAnalysis itself. This covers the remaining issues that tripped me up during debugging. After this, adding a refcount attribute to nodes is trivial! It will be annoying to review. I realize it should have been at least two PRs, but it's getting challenging to keep breaking things up and rebasing. In fact, a few test failures appeared in today's rebase that I'm still debugging. All remaining functional changes in subsequent PRs will be confined to the alias analysis queries. |
@eeckstein The two major take aways from this patch are:
|
@swift-ci test |
@swift-ci benchmark |
@swift-ci test source compatibility |
Build failed |
Build failed |
Performance: -O
Code size: -OPerformance: -Osize
Code size: -OsizePerformance: -Onone
Code size: -swiftlibsHow to read the dataThe tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.If you see any unexpected regressions, you should consider fixing the Noise: Sometimes the performance results (not code size!) contain false Hardware Overview
|
@swift-ci test |
1 similar comment
@swift-ci test |
Build failed |
Build failed |
@swift-ci test OS X platform |
@swift-ci benchmark |
Performance: -O
Code size: -OPerformance: -Osize
Code size: -OsizePerformance: -Onone
Code size: -swiftlibsHow to read the dataThe tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.If you see any unexpected regressions, you should consider fixing the Noise: Sometimes the performance results (not code size!) contain false Hardware Overview
|
Build failed |
PR testing repeatedly fails on SwiftSyntax which doesn't appear to be related to this PR. |
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.
See my comments in #28153 (review)
The two major take aways from this patch are: (1) Impose graph structure and reduce superfluous nodes and edges. Incrementally make the connection graph and the APIs used to construct it more structured. _This allows node properties based on the SILValue to be reliably added to nodes_ Although that was the initial motiviation, there are other benefits. Non-content nodes now have verifiable SILValues. Content nodes now have meaningful SILValues even though they can't be guaranteed due to merging. As a result it is *much* easier to debug the graph and correlate it with the SIL. Rather than a web of connection graph nodes with no identity and edges that don't correspond to anything in SIL, the graph nodes now have value number that correspond to the instruction used to dereference the node. The edges also exhibit structure now. A pointsTo edge now (in practice) always corresponds to a real pointer deference in the SIL. Doing this required more than just adding some helpers, it was also necessary to rewrite the graph merge and update algorithms. (2) Split up underlying functionality into more explicit steps Breaks apart the most complex parts of the graph algorithms into small self-contained, self-checked steps. The purpose of each step is clear and it's possible to reason about correctness from basic invariants. Each merge step can now run full graph verification. This was also done to move toward an invariant that the graph is never mutated during a query. But to finish that goal, we need to add a use-point query. With that, there will be no node creation, use point propagation, new defer edges, etc. after graph building. At the very least, this will make it sane to debug the output of the analysis. --- Here is a change-by-change description in diff order: Replace `updatePointsTo` with `initializePointsTo` and `mergePointsTo`. Merging is very simple on its own. Initialization requires some extra consideration for graph invariants. This separation makes it possible to write stong asserts and to independently reason about the correctness of each step based on static invariants. Replace `getContentNode` with `createContentNode`, and add two higher level APIs `createMergedContent`, and `getFieldContent`. This makes explicit the important cases of merging nodes and creating a special nodes for class fields. This slightly simplifies adding properties to content nodes and helps understand the structure of the graph. Factor out an `escapeContentsOf` helper for use elsewhere... Add a `getValueContent` helper. This is where we can tie the properties of content nodes to the address values that are used to address that content. This now also ensures that a Value node's value field is consistent with all SILValues that map to it. Add -escapes-internal-verify to check that the graph is in a valid state after every merge or update step. This verification drove the partial rewrite of mergeAllScheduledNodes. ConnectionGraph::defer implementation: explictly handle the three possible cases of pointsTo initialization or pointsTo merging at the top level, so that those underlying implementations do not need to dynamically handle weirdly different scenarios. ConnectionGraph::initializePointsTo implementation: this simplified implementation is possible by relying on invariants that can be checked at each merge/update step. The major functional difference is that it avoids creating unnecessary pointsTo edges. The previous implementation often created pointsTo edges when adding defer edges just to be conservative. Fixing this saved my sanity during debugging because the pointsTo edges now always correspond to a SIL operations that dereference the pointer. I'm also arguing without evidence that this should be much more efficient. ConnectionGraph::mergeAllScheduledNodes implementation: Add verification to each step so that we can prove the other utilities that are used while merging aren't making incorrect assumptions about the graph state. Remove checks for merged nodes now that the graph is consistently valid. Also remove a loop at the end that didn't seem to do anything. The diff is impossible to review, but the idea is basically the same. As long as it's still possible to scan through the steps in the new code without getting totally lost, then the goal was achieved. ConnectionGraph::mergePointsTo: This is extremely simple now. In all the places where we used to call updatePointsTo, and now call mergePointsTo, it's a lot easier for someone debugging the code to reason about what could possibly happen at that point. `createMergedContent` is a placeholder for transferring node properties. The `getFieldContent` helper may seem silly, but I find it helpful to see all the important ways that content can be created in one place next to the createContentNode, and I like the way that the creation of the special "field content" node is more explicit in the source. ConnectionGraph::mergeFrom implementation: this is only a minor cleanup to remove some control flow nesting and use the CGNodeWorklist abstraction. In AnalyzeInstruction, add EscapeAnalysis::getValueContent helper. It eliminates an extra step of going through the value node to get at its content node. This is where we can derive content node properties from the SILValue that dereferences the content. We can update the content node's associated value 'V' if it's useful. It's also a place to put assertions specific to the first level of content. In AnalyzeInstruction, Array semantic calls: add support for getValueContent so we can derive node properties. This is also nice because it's explicit about which nodes are value content vs. field content. In AnalyzeInstruction, cleanup Release handling: use the explicit APIs: getValueContent, getFieldContent, and escapeContentsOf. In AnalyzeInstruction, assert that load-like things can't produce addresses. In AnalyzeInstruction, add comments to clarify object projection handling. In AnalyzeInstruction, add comments to explain store handling. In AnalyzeInstruction, drop the assumption that all partial applies hold pointers. In AnalyzeInstruction, handle aggregates differently so that Value nodes are always consistent with their SILValue and can be verified. Aggregates nodes are still coalesced if they only have a single pointer-type subelement. If we arbitrarily coalesced an aggregate with just one of its subelements then there would be no consistent way to identify the value that corresponds to a connection graph node.
@swift-ci test and merge |
@swift-ci test |
Here is a change-by-change description in diff order:
Replace
updatePointsTo
withinitializePointsTo
andmergePointsTo
. Merging is very simple on its own. Initializationrequires some extra consideration for graph invariants. This
separation makes it possible to write stong asserts and to
independently reason about the correctness of each step based on
static invariants.
Replace
getContentNode
withcreateContentNode
, and add two higherlevel APIs
createMergedContent
, andgetFieldContent
. This makesexplicit the important cases of merging nodes and creating a special
nodes for class fields. This slightly simplifies adding properties to
content nodes and helps understand the structure of the graph.
Factor out an
escapeContentsOf
helper for use elsewhere...Add a
getValueContent
helper. This is where we can tie theproperties of content nodes to the address values that are used to
address that content. This now also ensures that a Value node's
value field is consistent with all SILValues that map to it.
Add -escapes-internal-verify to check that the graph is in a valid
state after every merge or update step. This verification drove the
partial rewrite of mergeAllScheduledNodes.
ConnectionGraph::defer implementation: explictly handle the three
possible cases of pointsTo initialization or pointsTo merging at the
top level, so that those underlying implementations do not need to
dynamically handle weirdly different scenarios.
ConnectionGraph::initializePointsTo implementation: this simplified
implementation is possible by relying on invariants that can be
checked at each merge/update step. The major functional difference is
that it avoids creating unnecessary pointsTo edges. The previous
implementation often created pointsTo edges when adding defer edges
just to be conservative. Fixing this saved my sanity during debugging
because the pointsTo edges now always correspond to a SIL operations
that dereference the pointer. I'm also arguing without evidence that
this should be much more efficient.
ConnectionGraph::mergeAllScheduledNodes implementation: Add
verification to each step so that we can prove the other utilities
that are used while merging aren't making incorrect assumptions about
the graph state. Remove checks for merged nodes now that the graph is
consistently valid. Also remove a loop at the end that didn't seem to
do anything. The diff is impossible to review, but the idea is
basically the same. As long as it's still possible to scan through the
steps in the new code without getting totally lost, then the goal was
achieved.
ConnectionGraph::mergePointsTo: This is extremely simple now. In all
the places where we used to call updatePointsTo, and now call
mergePointsTo, it's a lot easier for someone debugging the code to
reason about what could possibly happen at that point.
createMergedContent
is a placeholder for transferring node properties.The
getFieldContent
helper may seem silly, but I find it helpful tosee all the important ways that content can be created in one place
next to the createContentNode, and I like the way that the creation of
the special "field content" node is more explicit in the source.
ConnectionGraph::mergeFrom implementation: this is only a minor
cleanup to remove some control flow nesting and use the CGNodeWorklist
abstraction.
In AnalyzeInstruction, add EscapeAnalysis::getValueContent helper. It
eliminates an extra step of going through the value node to get at its
content node. This is where we can derive content node properties from
the SILValue that dereferences the content. We can update the content
node's associated value 'V' if it's useful. It's also a place to put
assertions specific to the first level of content.
In AnalyzeInstruction, Array semantic calls: add support for
getValueContent so we can derive node properties. This is also nice
because it's explicit about which nodes are value content vs. field
content.
In AnalyzeInstruction, cleanup Release handling: use the explicit
APIs: getValueContent, getFieldContent, and escapeContentsOf.
In AnalyzeInstruction, assert that load-like things can't produce addresses.
In AnalyzeInstruction, add comments to clarify object projection handling.
In AnalyzeInstruction, add comments to explain store handling.
In AnalyzeInstruction, drop the assumption that all partial applies hold pointers.
In AnalyzeInstruction, handle aggregates differently so that Value
nodes are always consistent with their SILValue and can be
verified. Aggregates nodes are still coalesced if they only have a
single pointer-type subelement. If we arbitrarily coalesced an
aggregate with just one of its subelements then there would be no
consistent way to identify the value that corresponds to a connection
graph node.