Skip to content

JIT: unpin locals whose value is provably non-movable#129110

Draft
AndyAyersMS wants to merge 2 commits into
dotnet:mainfrom
AndyAyersMS:jit-unpin-static-handle
Draft

JIT: unpin locals whose value is provably non-movable#129110
AndyAyersMS wants to merge 2 commits into
dotnet:mainfrom
AndyAyersMS:jit-unpin-static-handle

Conversation

@AndyAyersMS
Copy link
Copy Markdown
Member

Treat GTF_ICON_STATIC_HDL constants as no-gc defs, and add a new PHASE_UNPIN_LOCALS phase that runs after local morph and solves a small monotone forward dataflow tracking whether all defs of each local are no-gc (direct or via a LCL_VAR with the same property). Any pinned local whose value is provably non-movable is then unpinned, and the redundant pin/unpin stores get DCE'd.

The phase is a no-op for MinOpts and bails early when the method has no pinned locals. Catches chains like RVA-static or stackalloc threaded through a Span<>._reference temp; the existing lvaMarkLclRefs path continues to catch chains that only collapse to a direct no-gc value after morph constant propagation.

Fixes #13190.
Fixes #118802.
Fixes #127691.

Treat GTF_ICON_STATIC_HDL constants as no-gc defs, and add a new
PHASE_UNPIN_LOCALS phase that runs after local morph and solves a small
monotone forward dataflow tracking whether all defs of each local are
no-gc (direct or via a LCL_VAR with the same property). Any pinned
local whose value is provably non-movable is then unpinned, and the
redundant pin/unpin stores get DCE'd.

The phase is a no-op for MinOpts and bails early when the method has no
pinned locals. Catches chains like RVA-static or stackalloc threaded
through a Span<>._reference temp; the existing lvaMarkLclRefs path
continues to catch chains that only collapse to a direct no-gc value
after morph constant propagation.

Fixes dotnet#13190.
Fixes dotnet#118802.
Fixes dotnet#127691.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 8, 2026 01:45
@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 8, 2026
@AndyAyersMS
Copy link
Copy Markdown
Member Author

@EgorBo FYI

Hopefully you weren't working on #118802 -- I asked AI to find a "neglected" issue and work on it. And this is what it picked.

@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.

@AndyAyersMS
Copy link
Copy Markdown
Member Author

Also wondering if we should be retyping these locals, though that leads back to the land of mixed-type comparisons and such...

Copy link
Copy Markdown
Contributor

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 adds a new CoreCLR JIT optimization pass to clear lvPinned on locals whose definitions are proven to be GC-immovable (e.g., stack addresses, frozen handles, static/RVA handles), enabling later DCE to remove redundant pin/unpin stores and associated overhead.

Changes:

  • Extend GenTree::IsNotGcDef() to treat GTF_ICON_STATIC_HDL (and STATIC_HDL + offset) as non-GC / non-movable definitions.
  • Add a new JIT phase (PHASE_UNPIN_LOCALS) after local morph that computes a per-local “all defs are no-gc” property (with simple copy propagation) and unpins eligible locals.
  • Add new JIT regression test projects covering fixed-over-stackalloc and fixed-over-RVA-static scenarios.

Reviewed changes

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

Show a summary per file
File Description
src/coreclr/jit/gentree.h Classifies static handle constants (and handle+offset) as IsNotGcDef() to enable “no-gc” reasoning.
src/coreclr/jit/lclmorph.cpp Implements fgUnpinNonMovableLocals() monotone analysis and clears lvPinned for proven non-movable locals.
src/coreclr/jit/compphases.h Introduces PHASE_UNPIN_LOCALS in the phase list.
src/coreclr/jit/compiler.h Declares the new phase entry point.
src/coreclr/jit/compiler.cpp Wires PHASE_UNPIN_LOCALS into the compilation pipeline after local morph.
src/tests/JIT/opt/DSE/Regressions/PinnedStackAllocDoesNotEmitPinStore.csproj Adds a new JIT regression test project for stackalloc + fixed patterns.
src/tests/JIT/opt/DSE/Regressions/PinnedStackAllocDoesNotEmitPinStore.cs Adds functional tests that exercise stackalloc + fixed usage patterns.
src/tests/JIT/opt/DSE/Regressions/PinnedRvaStaticDoesNotEmitPinStore.csproj Adds a new JIT regression test project for RVA-static span patterns.
src/tests/JIT/opt/DSE/Regressions/PinnedRvaStaticDoesNotEmitPinStore.cs Adds functional tests that exercise RVA-static span + fixed usage patterns.

Comment on lines +2502 to +2506
JITDUMP("fgUnpinNonMovableLocals: %u local%s unpinned after %u iteration%s\n", unpinned, unpinned == 1 ? "" : "s",
iterations, iterations == 1 ? "" : "s");

return PhaseStatus::MODIFIED_NOTHING;
}
Comment on lines +60 to +76
[Fact]
public static void ReadFirst_ReturnsZero()
{
Assert.Equal(0, ReadFirst());
}

[Fact]
public static void Sum_ReturnsExpected()
{
Assert.Equal(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8, Sum());
}

[Fact]
public static void SumViaTwoFixed_ReturnsExpected()
{
Assert.Equal(1 + 8, SumViaTwoFixed());
}
@EgorBo
Copy link
Copy Markdown
Member

EgorBo commented Jun 8, 2026

@EgorBo FYI

Hopefully you weren't working on #118802 -- I asked AI to find a "neglected" issue and work on it. And this is what it picked.

For that specific one I have #119674 PR which is basically ready, need to clarify NAOT behavior. I am not sure yours is correct in

/ Address of static data is not movable by the GC.
        if (IsIconHandle(GTF_ICON_STATIC_HDL) || (OperIs(GT_ADD) && gtGetOp1()->IsIconHandle(GTF_ICON_STATIC_HDL)))

assumption?

E.g for unloadable ALC context where pinning effectively keeps it alive

@AndyAyersMS
Copy link
Copy Markdown
Member Author

E.g for unloadable ALC context where pinning effectively keeps it alive

I will leave that part for your PR. The dataflow analysis provides the bulk of the benefit here.

Remove the GTF_ICON_STATIC_HDL arm of IsNotGcDef. As @EgorBo noted on
dotnet#129110, the static-data address can live in a collectible ALC's loader
heap, where pinning may be what's keeping the LoaderAllocator reachable.
The safe answer is the canOmitPinning JIT-EE API in dotnet#119674, which asks
the VM to verify !LoaderAllocator->CanUnload().

The stackalloc fix in PHASE_UNPIN_LOCALS is unaffected: it still picks
up the GT_LCL_ADDR path that was already in IsNotGcDef. SPMI asmdiffs
loses only ~3K bytes out of -190K from this revert.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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

3 participants