JIT: Canonicalize loop backedges to a single latch block#128303
JIT: Canonicalize loop backedges to a single latch block#128303AndyAyersMS wants to merge 2 commits into
Conversation
Adds optCanonicalizeBackedges, run during optCanonicalizeLoops between preheader creation and exit canonicalization. For any natural loop with more than one backedge, this creates a new latch block that branches to the header and redirects every backedge source to the latch. The result is a canonical single-backedge loop shape that simplifies downstream loop optimizations. The latch is created with fgNewBBinRegion(BBJ_ALWAYS, header) so it inherits the header's try/handler region. We bail out if any backedge source is in a different EH region than the header, since a source that can legally branch to the header (e.g. into the entry of a try region) cannot necessarily branch to a non-entry block inside that region. SPMI replay across 558k methods: 0 failures. SPMI asmdiffs: net code size roughly neutral (-11 KB across ~1 GB), with PerfScore improving in 8 of 11 collections by 2.8% to 28%. Top size regressions are paired with large PerfScore wins (e.g. ReportExportedTypeNameCollisions: +310 bytes for -44% PerfScore), driven by downstream passes (RA, hoisting, IV opts) exploiting the canonical loop shape. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull request overview
This PR extends CoreCLR JIT loop canonicalization by introducing a new step that merges multiple natural-loop backedges into a single “latch” block that unconditionally branches to the loop header, enabling downstream optimizations to rely on a single canonical backedge shape.
Changes:
- Invoke a new backedge-canonicalization step during
Compiler::optCanonicalizeLoops()after preheader creation and before exit canonicalization. - Implement
Compiler::optCanonicalizeBackedges(FlowGraphNaturalLoop*)to create a latch block and redirect all backedge sources through it (with EH-region legality checks). - Add the new helper’s declaration to
compiler.h.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/coreclr/jit/optimizer.cpp | Adds the new canonicalization phase and implements latch creation + backedge redirection logic. |
| src/coreclr/jit/compiler.h | Declares optCanonicalizeBackedges on Compiler. |
|
This would close #107189. Note there is an implementation of this canonicalization on that issue too, but that one had large regressions. |
|
diffs seem quite reasonable. This is a bit less ambitious than https://github.com/jakobbotsch/runtime/pull/new/canonicalize-backedges as it doesn't try and handle the case where the back edges come from enclosing regions. I can try extending it to that case. I was hoping perhaps for a bit more code reduction as this should simplify LSRA's need to put resolution moves in multiple places and also gives it a natural well-placed block to hold them (that is, back edges are quite often critical edges, coming from a conditional exit block to the join at the header). |
|
Yes, perhaps we have fixed something else since I tried it. I recall this broke loop cloning when I tried, but it doesn't seem like it from the metric diffs in this PR. |
It seems likely that we are removing these blocks. Perhaps what has changed since I tried is that we do that sooner. |
Mirror optCreatePreheader's region selection: when any backedge source is outside the header's try region, place the latch in the header's true enclosing try region (only legal because the header must be a try entry in that case). Replaces the previous all-or-nothing sameEHRegion bail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Did this locally and it looks good too, so updated the changes. |
|
Updated diffs |
|
@jakobbotsch PTAL One last bit of loop canonicalization... |
Adds optCanonicalizeBackedges, run during optCanonicalizeLoops between preheader creation and exit canonicalization. For any natural loop with more than one backedge, create a new block that branches to the header and redirect every backedge source to the block.