Skip to content

JIT: Add an IV opt that replaces primary IVs with enregisterable ones #129305

Draft
jakobbotsch wants to merge 4 commits into
dotnet:mainfrom
jakobbotsch:iv-replace-with-enregisterable
Draft

JIT: Add an IV opt that replaces primary IVs with enregisterable ones #129305
jakobbotsch wants to merge 4 commits into
dotnet:mainfrom
jakobbotsch:iv-replace-with-enregisterable

Conversation

@jakobbotsch

@jakobbotsch jakobbotsch commented Jun 11, 2026

Copy link
Copy Markdown
Member

This optimization detects when a primary IV will not be enregistered based on DNER or due to being EH live, and if possible, creates a new primary IV to split its lifetime to make it enregisterable.

Fixes #124285

This optimization detects when a primary IV will not be enregistered
based on DNER or due to being EH live, and if possible, creates a new
primary IV to split its lifetime to make it enregisterable.
Copilot AI review requested due to automatic review settings June 11, 2026 17:59
@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 11, 2026
Comment on lines +1290 to +1300
// We must be able to insert the store-back of the final value into every
// regular exit where the IV is live. Blocks that are part of a call-finally
// pair must remain empty, so we cannot sink into them.
BasicBlockVisit exitResult = loop->VisitRegularExitBlocks([=](BasicBlock* exit) {
if (optLocalIsLiveIntoBlock(lclNum, exit) && (exit->isBBCallFinallyPair() || exit->isBBCallFinallyPairTail()))
{
return BasicBlockVisit::Abort;
}

return BasicBlockVisit::Continue;
});

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We should change exit canonicalization to make sure there don't exist as exits, but I think it can be a follow up.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 extends CoreCLR JIT induction-variable optimizations by adding a late pass that can replace primary IV locals that won’t be enregistered (DNER or EH live-in/out-of-handler) with a fresh temp whose lifetime is constrained to the loop body, and adds a regression test covering try/finally and try/catch interaction.

Changes:

  • Add a new IV replacement pass in optInductionVariables() plus helper legality/liveness checks and an in-loop local replacement walker.
  • Add a new JIT metadata metric (EnregisterableIVsCreated) to track how many replacements were performed.
  • Add a new JIT regression test (PrimaryIVLiveInHandler) validating correctness across finally paths, throwing loops, and catch-resume paths.

Reviewed changes

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

Show a summary per file
File Description
src/coreclr/jit/inductionvariableopts.cpp Implements the new unenregisterable-primary-IV replacement optimization and hooks it into the IV opts phase.
src/coreclr/jit/compiler.h Declares the new Compiler::opt* helpers used by the replacement pass.
src/coreclr/jit/jitmetadatalist.h Adds a new metadata metric to report how many enregisterable IV replacements were created.
src/tests/JIT/opt/Loops/PrimaryIVLiveInHandler.cs Adds regression coverage for IV replacement correctness with try/finally and try/catch shapes.
src/tests/JIT/opt/Loops/PrimaryIVLiveInHandler.csproj Adds the project file to build the new JIT test.

Comment on lines +1290 to +1306
// We must be able to insert the store-back of the final value into every
// regular exit where the IV is live. Blocks that are part of a call-finally
// pair must remain empty, so we cannot sink into them.
BasicBlockVisit exitResult = loop->VisitRegularExitBlocks([=](BasicBlock* exit) {
if (optLocalIsLiveIntoBlock(lclNum, exit) && (exit->isBBCallFinallyPair() || exit->isBBCallFinallyPairTail()))
{
return BasicBlockVisit::Abort;
}

return BasicBlockVisit::Continue;
});

if (exitResult == BasicBlockVisit::Abort)
{
JITDUMP(" Cannot replace V%02u; a live regular exit is part of a call-finally pair\n", lclNum);
return false;
}
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@jakobbotsch

Copy link
Copy Markdown
Member Author

SuperPMI collections show:

EnregisterableIVsCreated
Collection Base Diff PDIFF
aspnet2.run.linux.x64.checked.mch 0 10 0.00%
benchmarks.run.linux.x64.checked.mch 0 17 0.00%
benchmarks.run_pgo.linux.x64.checked.mch 0 10 0.00%
benchmarks.run_pgo_optrepeat.linux.x64.checked.mch 0 17 0.00%
coreclr_tests.run.linux.x64.checked.mch 0 152 0.00%
libraries.crossgen2.linux.x64.checked.mch 0 41 0.00%
libraries.pmi.linux.x64.checked.mch 0 112 0.00%
libraries_tests.run.linux.x64.Release.mch 0 1,182 0.00%
libraries_tests_no_tiered_compilation.run.linux.x64.Release.mch 0 114 0.00%
realworld.run.linux.x64.checked.mch 0 23 0.00%

Might be worth taking.

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.

[JIT] JIT regression in .NET 10: foreach over Span<T> field in ref struct triggers excessive inlining of ArrayPool internals

2 participants