Skip to content

JIT: unpin locals fed by non-GC typed values#130002

Closed
AndyAyersMS wants to merge 1 commit into
dotnet:mainfrom
AndyAyersMS:unpin-span-from-pointer-129993
Closed

JIT: unpin locals fed by non-GC typed values#130002
AndyAyersMS wants to merge 1 commit into
dotnet:mainfrom
AndyAyersMS:unpin-span-from-pointer-129993

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Treat a non-GC typed def (eg a native int reinterpreted as a byref, as in a Span built from a pointer) as non-movable, so the pinned local can be unpinned. Fixes #129993.

Treat a non-GC typed def (eg a native int reinterpreted as a byref, as
in a Span built from a pointer) as non-movable, so the pinned local can
be unpinned. Fixes dotnet#129993.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 29, 2026 21:28
@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 29, 2026
@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 AndyAyersMS requested a review from EgorBo June 29, 2026 21:36
@AndyAyersMS

Copy link
Copy Markdown
Member Author

@EgorBo PTAL
50-60 methods with diffs.

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 updates the JIT’s “no-GC definition” classification used by pinned-local unpinning so that locals fed by certain non-GC-typed values can be treated as non-movable, enabling pin elision in more cases.

Changes:

  • Expands GenTree::IsNotGcDef() to early-return true when the node’s TypeGet() is not GC-typed.

Comment thread src/coreclr/jit/gentree.h
Comment on lines +1225 to +1229
// A non-GC typed value (eg native int reinterpreted as byref) cannot designate a movable object.
if (!varTypeIsGC(TypeGet()))
{
return true;
}
Comment thread src/coreclr/jit/gentree.h
Comment on lines +1225 to +1229
// A non-GC typed value (eg native int reinterpreted as byref) cannot designate a movable object.
if (!varTypeIsGC(TypeGet()))
{
return true;
}
Comment thread src/coreclr/jit/gentree.h
bool IsNotGcDef() const
{
// A non-GC typed value (eg native int reinterpreted as byref) cannot designate a movable object.
if (!varTypeIsGC(TypeGet()))

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.

I assume this sort of marks any untracked GC pointer as UB even if previously it worked, like

fixed (... = Unsafe.AsPointer(gcRef))

cc @dotnet/jit-contrib in case if you see any concern

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.

any concern

This is a breaking change. I don't see how it can be justified, since the pinned state of a value is a dynamic property. Here we only know that the value is pinned at the def point, but we need to know that it "would be" pinned for at least the lifetime the caller is concerned about.

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.

ECMA spec explicitly talks about "Start GC tracking" and "Stop GC tracking" for conversions.

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.

Yes this is too broad.

Seem like though that a non-gc parameter could possibly be handled here. If the parameter actually referred to GC memory whoever passed the argument would need to have pinned or they'd already be broken.

@SingleAccretion SingleAccretion Jun 29, 2026

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.

If the parameter actually referred to GC memory whoever passed the argument would need to have pinned or they'd already be broken.

I don't think parameters are special in any way. The pinning state can be dynamically altered for them just as for normal variables (through opaque means such as calls, inter-thread sync, GC handles, aliased pointers, etc).

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.

It seems to me the analysis is also reliant on the "global" (or at least per-method) perspective, since a "true" return value is treated as representing a value that can't be made movable 'no matter what'. It would require a very sophisticated analysis indeed to prove that the pinned state of a value [that may be movable] doesn't change beyond its use in a def, in the general case.

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.

As others have seemingly already mentioned here 😅: #129993 (comment)

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.

The global analysis is flow insensitive so def vs use ordering shouldn't matter?

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.

The global analysis is flow insensitive so def vs use ordering shouldn't matter?

What I meant is basically this:

void* p = GetPin(); // 'p' pinned here via e. g. a handle, the virtual 'pin ref count' increases: 0 -> 1

pinned j = p; // It increases again: 1 -> 2
Unpin(p); // Decreases: 2 -> 1

// ...
// 'j's value still pinned here
// ...

return; // Decreases again: 1 -> 0, unpinned.

as the situation where the pinned state (analogous to a ref count) "changes beyond its use in a def [here the j = p def]". We know that the value represented by p at the point of the def must be pinned [otherwise we have UB], but beyond that point it must be proven by some other means.

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.

Thanks all. Didn't appreciate that we have these non-lexical pinning mechanisms which makes any kind of local inference unsound.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot -nuget

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.

Elide pinning over spans from pointers

7 participants