Skip to content

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

Draft
jakobbotsch wants to merge 12 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 12 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.

jakobbotsch and others added 5 commits June 12, 2026 10:51
…ther IV opts

Run the enregisterable primary IV replacement before strength reduction and
widening, and construct the new IV in proper SSA form via IncrementalSsaBuilder
so those optimizations can apply to it. Delete the old IV's header phi so the
scalar evolution analysis does not look at the now-storeless old IV.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Occurrences are collected from non-phi statements only, so the header phi is
never visited during the rename.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Forward declare IncrementalSsaBuilder and UseDefLocation in compiler.h.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 12, 2026 09:05
jakobbotsch and others added 2 commits June 12, 2026 11:11
Mirror the check in optCanSinkWidenedIV: if a regular exit the IV is live into
has a predecessor from outside the loop, the store-back of the final value
would incorrectly overwrite the original local on a non-loop path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment on lines +1 to +9
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DebugType>PdbOnly</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
Comment thread src/coreclr/jit/inductionvariableopts.cpp
Avoid carrying over stale data (SSA numbers, value numbers) from the old
local's nodes by allocating new store/use nodes for the new local.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 12, 2026 09:21

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

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

Comment on lines +1238 to +1241
if (!optCanReplaceUnenregisterablePrimaryIV(lclNum, loop))
{
return false;
}
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