Skip to content

JIT: Generalize flow edge redirection logic #116933

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

Merged
merged 2 commits into from
Jun 27, 2025

Conversation

amanasifkhalid
Copy link
Member

Replace fgRedirect*Edge helpers with an implementation agnostic to the source block's jump kind.

I was motivated to do this consolidation while looking at replacing a few flow optimizations with block compaction. For example, fgOptimizeSwitchBranches shouldn't be necessary when we can easily compact the switch's successors. However, block compaction currently skips blocks with switch predecessors to avoid invalidating the switch descriptor map. If we add support for redirecting switch successor edges, we can update their flow without needing to modify the map most of the time (and I've found from my local work that when we do need to update the map, it's quite easy to do).

@Copilot Copilot AI review requested due to automatic review settings June 23, 2025 20:16
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 23, 2025
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

@Copilot Copilot AI left a 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 consolidates the JIT’s flow-edge redirection helpers into a single, implementation-agnostic fgRedirectEdge method and updates all call sites accordingly.

  • Replaces fgRedirectTargetEdge, fgRedirectTrueEdge, and fgRedirectFalseEdge with fgRedirectEdge at every call site.
  • Adds GetTargetEdgeRef(), GetTrueEdgeRef(), and GetFalseEdgeRef() to BasicBlock and updates the Compiler interface.
  • Adjusts switch-case lowering to manually reset duplicate counts instead of using the old helpers.

Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
rangecheckcloning.cpp Swapped fgRedirectTargetEdge for fgRedirectEdge.
optimizer.cpp Updated conditional and loop-redirection calls.
optimizebools.cpp Replaced boolean-optimization redirection.
objectalloc.cpp Changed object-allocation clone redirection.
morph.cpp Unified Qmark-statement flow updates.
lower.cpp Refactored switch-case successor count adjustments.
jiteh.cpp Swapped EH-region edge redirections.
indirectcalltransformer.cpp Rewrote cold-path edge redirection.
importer.cpp Updated impImportLeave* redirections.
helperexpansion.cpp Unified runtime-lookup redirections.
async.cpp Simplified resumption-switch redirection.
flowgraph.cpp Replaced target-edge helper and cleaned up unreachable case.
fgopt.cpp Streamlined post-import cleanup and branch optimizations.
fginline.cpp Updated inline insertion redirection.
fgflow.cpp Removed specialized redirection methods; added generic one.
fgehopt.cpp Replaced finally-optimization edge redirections.
fgbasic.cpp Adjusted jump-target replacement logic.
compiler.h Updated Compiler API to expose fgRedirectEdge.
block.h Added methods to obtain edge references on BasicBlock.
Comments suppressed due to low confidence (2)

src/coreclr/jit/fgflow.cpp:370

  • [nitpick] The documentation comment still references the outdated behavior for a generic redirect; consider updating this comment to reflect that it now handles true, false, and unconditional edges uniformly and remove references to parameters that no longer exist.
// fgRedirectEdge: Sets the given edge's target block to 'newTarget',

src/coreclr/jit/rangecheckcloning.cpp:283

  • It would be helpful to add or extend unit tests that verify fgRedirectEdge works correctly for a simple range-check cloning scenario, ensuring edge redirection preserves expected control flow and weights.
    comp->fgRedirectEdge(prevBb->GetTargetEdgeRef(), lowerBndBb);

@@ -1550,14 +1550,13 @@ bool Lowering::TryLowerSwitchToBitTest(FlowEdge* jumpTable[],
#endif

//
// Rewire the blocks as needed.
// Set successor edge dup counts to 1 each
//
Copy link
Preview

Copilot AI Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider adding a comment explaining why the successor edge duplicate counts are adjusted manually here instead of using the existing helpers (fgRemoveAllRefPreds/fgAddRefPred) to improve readability and maintainability.

Suggested change
//
//
// Note: The successor edge duplicate counts are adjusted manually here instead of using
// the existing helpers (`fgRemoveAllRefPreds`/`fgAddRefPred`) because this operation
// is part of a tightly coupled optimization that directly modifies edge counts and
// references. Using the helpers in this context would introduce unnecessary overhead
// and complexity, as they are designed for more general-purpose scenarios.

Copilot uses AI. Check for mistakes.

@amanasifkhalid
Copy link
Member Author

@dotnet/jit-contrib PTAL. No asmdiffs. Aside from a MinOpts outlier in coreclr_tests, the TP diffs are small. The generalized implementation has an extra branch compared to fgRedirectTargetEdge (i.e. the single-successor case), but this seems to make a tangible difference infrequently enough that I don't think it's worth templating this branch away.

Comment on lines +864 to +870
FlowEdge*& GetTargetEdgeRef()
{
assert(HasInitializedTarget());
assert(bbTargetEdge->getSourceBlock() == this);
assert(bbTargetEdge->getDestinationBlock() != nullptr);
return bbTargetEdge;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most other "ref" getters do not have "Get" prefix, e.g. NodeRef, ReturnValueRef, ArrRef. Should we follow the same convention here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The choice is between following that convention, or the naming scheme of the other BasicBlock getters (GetTargetEdge, etc.) -- which would you prefer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jakobbotsch just following up on this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the argument for not having the "Get" prefix is that you can both get and set the field through the reference. CallArg has both GetNode() and NodeRef() for example. I think it would make sense to follow the same convention here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll open a follow-up to switch the naming.

@amanasifkhalid
Copy link
Member Author

ping @dotnet/jit-contrib

@amanasifkhalid amanasifkhalid merged commit b738611 into dotnet:main Jun 27, 2025
108 of 110 checks passed
@amanasifkhalid amanasifkhalid deleted the fgRedirectEdge branch June 27, 2025 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants