Skip to content

Remove redundant pinning over static fields/RVA - #119674

Open
EgorBo wants to merge 12 commits into
dotnet:mainfrom
EgorBo:avoid-pinning-rva
Open

Remove redundant pinning over static fields/RVA#119674
EgorBo wants to merge 12 commits into
dotnet:mainfrom
EgorBo:avoid-pinning-rva

Conversation

@EgorBo

@EgorBo EgorBo commented Sep 12, 2025

Copy link
Copy Markdown
Member

I noticed that sometimes developers make assumptions that it's fine to use Unsafe.AsPointer over RVA fields since they're not movable, which is fairly unreliable thing to do (e.g. Unloadable ALCs). So let's just optimize the pinning overhead in JIT when we know it's safe to do so:

// Case 0: RVA fields
void Test0()
{
    ReadOnlySpan<int> rva = [1,2,3,4,5];
    fixed (int* p = rva)
        Consume(p);
}

// Case 1: Primitive static fields (unmanaged memory)
static int _primitiveFld;
void Test1()
{
    fixed (int* p = &_primitiveFld)
        Consume(p);
}


// Case 2: Struct static fields (boxed and stored on the NonGC heap)
static MyStruct _structFld;
void Test2()
{
    fixed (int* p = &_structFld.Y)
        Consume(p);
}

Codegen diff: https://www.diffchecker.com/LgIALnAn/

@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 Sep 12, 2025
@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.

@EgorBo

EgorBo commented Sep 13, 2025

Copy link
Copy Markdown
Member Author

@MihuBot

private bool canOmitPinning(CORINFO_FIELD_STRUCT_* fldHnd)
{
FieldDesc field = HandleToObject(fldHnd);
if (!field.IsStatic || field.IsThreadStatic || field.HasGCStaticBase || field.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

HasGCStaticBase condition should not be needed for NativeAOT.

See dotnet/runtimelab#870

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.

I see, thanks. Removed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

HasGCStaticBase condition is needed for R2R to be functionally correct. Are we seeing any crashes in GCStress+R2R with this change? If not, we may consider adding some targeted tests to validate that this optimization is valid.

It is an interesting question how conservative the contract should be for R2R. I guess we can start with the condition you had here originally. It will be one more reason why R2R is incompatible and ignored for collectible ALCs.

@EgorBo
EgorBo marked this pull request as ready for review September 30, 2025 22:27
Copilot AI review requested due to automatic review settings September 30, 2025 22:27
@EgorBo

EgorBo commented Sep 30, 2025

Copy link
Copy Markdown
Member Author

@jkotas could you please review the VM side? I guess I have a few conservative checks like ThreadStatics, but I was mostly focusing on RVA fields and structs (boxed statics)
@dotnet/jit-contrib PTAL jit side

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 optimizes pinning operations in the JIT compiler by introducing a new canOmitPinning API that allows the JIT to skip pinning overhead when working with static fields that are guaranteed to be stable in memory. The optimization applies to RVA fields, primitive static fields in unmanaged memory, and struct static fields stored on the NonGC heap.

Key changes:

  • Introduces canOmitPinning JIT interface method to determine when pinning can be safely omitted
  • Enhances IsNotGcDef to consider field sequences and call the new API for static fields
  • Updates SuperPMI infrastructure to support the new interface method

Reviewed Changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/coreclr/vm/jitinterface.cpp Implements canOmitPinning with logic to check if static fields are safe to use without pinning
src/coreclr/jit/gentree.cpp Refactors IsNotGcDef to accept compiler parameter and check static field sequences
src/coreclr/jit/gentree.h Changes IsNotGcDef signature to require compiler parameter
src/coreclr/jit/lclvars.cpp Updates call to IsNotGcDef with compiler parameter
src/coreclr/inc/corinfo.h Adds canOmitPinning method declaration to JIT interface
src/coreclr/inc/jiteeversionguid.h Updates JIT-EE version identifier for interface change
src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Implements simplified AOT version of canOmitPinning
src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs Auto-generated code for new interface method
Various SuperPMI files Adds infrastructure support for recording and replaying canOmitPinning calls

@EgorBo
EgorBo requested a review from jkotas September 30, 2025 22:27
Comment thread src/coreclr/jit/gentree.cpp
Comment thread src/coreclr/vm/jitinterface.cpp Outdated
Comment thread src/coreclr/vm/jitinterface.cpp Outdated
private bool canOmitPinning(CORINFO_FIELD_STRUCT_* fldHnd)
{
FieldDesc field = HandleToObject(fldHnd);
if (!field.IsStatic || field.IsThreadStatic || field.HasGCStaticBase || field.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

HasGCStaticBase condition is needed for R2R to be functionally correct. Are we seeing any crashes in GCStress+R2R with this change? If not, we may consider adding some targeted tests to validate that this optimization is valid.

It is an interesting question how conservative the contract should be for R2R. I guess we can start with the condition you had here originally. It will be one more reason why R2R is incompatible and ignored for collectible ALCs.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jun 8, 2026
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>
Resolve the JIT-EE conflicts, incorporate review feedback, and add JIT, ReadyToRun, NativeAOT, and GC stress coverage for static pinning.

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

Copilot-Session: 6626f902-a2c1-4f01-a35b-0981b4dcc0e7
Copilot AI review requested due to automatic review settings July 16, 2026 08:17

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 29 out of 29 changed files in this pull request and generated 2 comments.

@EgorBo

EgorBo commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

@MihuBot -nuget

Comment thread src/coreclr/tools/aot/ILCompiler.Compiler.Tests/JitInterfaceTests.cs Outdated
Comment thread src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
# Conflicts:
#	src/coreclr/inc/corinfo.h
#	src/coreclr/inc/icorjitinfoimpl_generated.h
#	src/coreclr/inc/jiteeversionguid.h
#	src/coreclr/jit/ICorJitInfo_names_generated.h
#	src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp
#	src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs
#	src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt
#	src/coreclr/tools/aot/jitinterface/jitinterface_generated.h
#	src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h
#	src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h
#	src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp
#	src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp
Copilot AI review requested due to automatic review settings July 17, 2026 12:10

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 25 out of 25 changed files in this pull request and generated 2 comments.

Comment thread src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h
Comment thread src/coreclr/inc/corinfo.h
@github-actions

This comment was marked as spam.

github-actions[bot]

This comment was marked as spam.

@EgorBo

EgorBo commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

@jkotas @MichalStrehovsky anything else here on the VM side?

@MichalStrehovsky MichalStrehovsky left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The native AOT part looks good to me.

I don't know enough about R2R/CoreCLR VM to sign off on that part. My question marks for R2R/VM:

  1. do we have guarantees the R2R module is not loaded into unloadable ALC?
  2. it's not clear to me under what condition the statics are immovable. per #129664 CoreCLR will not create an immovable static for primitive types even if asked nicely, but here we seem to be saying canOmitPinning is true for them.

Comment thread src/coreclr/inc/corinfo.h
void* address
) = 0;

// Returns true iff pinning of the field's address can be elided because runtime guarantees stability.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// Returns true iff pinning of the field's address can be elided because runtime guarantees stability.
// Returns true iff pinning of the field's address can be elided because runtime guarantees the field
// address won't move and the field storage won't be collected.

Be more specific about what this actually guarantees

Comment thread src/coreclr/jit/gentree.h

return false;
}
bool IsNotGcDef(Compiler* comp) const;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add a comment about what this returns?

@EgorBo

EgorBo commented Jul 22, 2026

Copy link
Copy Markdown
Member Author
  1. do we have guarantees the R2R module is not loaded into unloadable ALC?

My understanding we do not support R2R when we load code into an unloadable ALC today. if there is an API I can call to bail out unloadble ALC in R2R - I can call, but I don't see any. I have a suspicion (not based on anything) we have a lot of challenges to solve to enable r2r for that.

For CoreCLR we bail out via fieldOwnerType.GetLoaderAllocator()->CanUnload() check

  1. it's not clear to me under what condition the statics are immovable. per FixedAddressValueType is silently ignored when not supported #129664 CoreCLR will not create an immovable static for primitive types even if asked nicely, but here we seem to be saying canOmitPinning is true for them.

I am not sure I fully understand the issue, my understanding that they're always allocated on non-gc memory (nongc primitives). NonGC non-primitives are typically allocated on FOH (boxed statics).

@jkotas

jkotas commented Jul 22, 2026

Copy link
Copy Markdown
Member

My understanding we do not support R2R when we load code into an unloadable ALC today.

Right.

we have a lot of challenges to solve to enable r2r for that.

Known issues would not be that hard to solve. The testing/reliability (finding all issues) is the harder part.

CoreCLR will not create an immovable static for primitive types even if asked nicely

Primitive statics are movable only in collectible assemblies in the current main.


#if READYTORUN
// RVA data and reference-type static slots are always stable. Keep value-type GC
// statics conservative because ReadyToRun can store them in movable boxes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// statics conservative because ReadyToRun can store them in movable boxes.
// statics conservative because the runtime can store them in movable boxes.

@jkotas

jkotas commented Jul 22, 2026

Copy link
Copy Markdown
Member

The VM side looks fine to me.

Speaking about pinning, I wish for #63397. It should be able to produce similar codegen diffs in a lot more cases.

@EgorBo EgorBo added this to the 12.0.0 milestone Jul 30, 2026
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 reduce-unsafe

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants