-
Notifications
You must be signed in to change notification settings - Fork 5.2k
JIT: Try to maintain fallthrough in Compiler::fgSplitEdge
#107419
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
base: main
Are you sure you want to change the base?
Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
|
cc @dotnet/jit-contrib, @AndyAyersMS PTAL. Diffs are large, though they're inflated by collections with tiering ( |
|
Seems like perhaps That being said, I have seen those LSRA blocks end up very poorly placed, and we might question whether after source or before target is the right location for the new block, when source and target blocks are not adjacent, or whether we just (as we've discussed) redo layout if LSRA makes changes. |
|
I suppose we could differentiate between before/after layout, though since we're seeing this block placement affect FullOpts with some frequency, it might be more worthwhile to get layout working after LSRA.
Between the two, I think placing after source is better most of the time? If the target has multiple preds, placing before target could potentially break up hotter fallthrough. If the source has only one successor, then placing after the source doesn't change anything, but if the source is a If you'd like, I can put this PR on-hold and work on getting layout working after LSRA. For that, I'm thinking we can continue to run layout before LSRA, and detect if we need to run it again after, just to minimize regressions. Once we've refactored switch recognition to not depend on lexical layout (#107076), we should be able to only run layout once, after LSRA. |
|
I think it makes sense to look into fixing layout post LSRA first. If that turns out to be complicated, then perhaps we can reconsider and do this first. I believe LSRA will only split "critical edges"— edges where the source has multiple flow successors and the target has multiple flow predecessors. If we trust the initial layout then if source and target are adjacent then putting the block in between is the right thing; if not, presumably both source and target have other preferred partners and if those partners are adjacent then the new block should be placed out of the way somewhere where it won't break up any other important flow (though not necessarily at the end of the method / region, which is what I commonly see). If the preferred source or target partner is not adjacent (or maybe if no successor/predecessor is adjacent), then we should be placing after source or before target depending. This calculus would be easier if we hand the flow scoring that we are envisioning for k-opt, as we could evaluate the different options and pick the one that has the best score. |
|
With #107483 merged, the diffs are still big, though FullOpts diffs are concentrated in @AndyAyersMS are you ok taking this change as-is, or would you prefer we whittle down the churn as much as possible? |
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.
Pull Request Overview
This PR modifies the JIT's fgSplitEdge method to better maintain fallthrough when splitting edges during LSRA (Linear Scan Register Allocation). The key improvement is placing the new intermediary block more strategically to preserve existing control flow patterns and avoid introducing unnecessary branches.
- Improves block placement strategy in
fgSplitEdgeto maintain fallthrough when possible - Simplifies weight calculation logic by using existing predecessor edge weight directly
- Updates documentation to reflect the cleaner implementation
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/coreclr/jit/fgbasic.cpp | Refactors fgSplitEdge to place new blocks more strategically and simplifies weight calculation |
| src/coreclr/jit/lsra.cpp | Updates comment to reflect simplified edge splitting behavior |
|
I decided to take another look at this, now that our block layout story is mature. The diffs show large size decreases for both optimized and unoptimized code, though the bulk of these savings are from instrumented tiers. Here's the breakdown:
@AndyAyersMS the churn might be too large to take at this point, but I think this is worth taking at some point, considering the potential size savings. |
Given some branch
curr -> succ,fgSplitEdgeintroduces an intermediary block to create the formcurr -> newBlock -> succ. The lexical placement ofnewBlockwouldn't matter if it weren't for the fact that we callfgSplitEdgeduring LSRA, after we've reordered blocks. Ideally, we wouldn't introduce any new blocks after establishing layout, but for the time being, always placingnewBlockaftercurrto create fallthrough -- effectively moving thecurr -> succbranch up a block, and not introducing new branches -- seems to be a decent compromise.