Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove ref struct pinning from locals #97997

Closed
wants to merge 2 commits into from

Conversation

MichalPetryka
Copy link
Contributor

Removes pinned from ref struct locals as refs to them can't point to the heap legally.

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Feb 5, 2024
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Feb 5, 2024
@ghost
Copy link

ghost commented Feb 5, 2024

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

Issue Details

Removes pinned from ref struct locals as refs to them can't point to the heap legally.

Author: MichalPetryka
Assignees: -
Labels:

area-CodeGen-coreclr, community-contribution

Milestone: -

@@ -275,7 +275,26 @@ void Compiler::lvaInitTypeRef()

if ((corInfoTypeWithMod & CORINFO_TYPE_MOD_PINNED) != 0)
{
if ((corInfoType == CORINFO_TYPE_CLASS) || (corInfoType == CORINFO_TYPE_BYREF))
CORINFO_CLASS_HANDLE refClsHnd = NO_CLASS_HANDLE;
// don't run in crossgen to avoid invalid type info
Copy link
Member

Choose a reason for hiding this comment

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

What is the invalid type info problem with crossgen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

R2R could be compiled against an assembly version where the type was flagged as byref-like while it was changed to a normal struct later.

Copy link
Member

Choose a reason for hiding this comment

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

Changing byref-like struct to non-byref like struct is not a compatible change. Once the struct is byref-like in public surface, it has to stay byref-like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Even if so, it's safer to just skip running it there.

Copy link
Member

Choose a reason for hiding this comment

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

We want AOT compilers to have same optimizations as JIT where possible.

if ((refClsHnd != NO_CLASS_HANDLE) &&
((info.compCompHnd->getClassAttribs(refClsHnd) & CORINFO_FLG_BYREF_LIKE) != 0))
{
JITDUMP("Ignoring pinned on local V%02u for byref-like type %s\n", varNum, eeGetClassName(refClsHnd));
Copy link
Contributor Author

@MichalPetryka MichalPetryka Feb 5, 2024

Choose a reason for hiding this comment

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

@jkotas We were having a discussion on Discord whether enforcing ref struct refs not pointing to the heap is an acceptable assumption for the JIT to make, could you give your opinion on the safety of this change?

Copy link
Member

Choose a reason for hiding this comment

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

It is fine to assume that ref struct is pinned and allocated on the stack. It would be a major breaking change (for the R2R format at least) to allocate ref structs on the GC heap.

Copy link
Member

Choose a reason for hiding this comment

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

The concern is about aliasing. Before this change the JIT's aliasing model allows roundtripping any ref A as a ref B. The optimization in this PR is not valid unless we consider that to be UB when A and B do not match in byref likeness.

@ryujit-bot
Copy link

Diff results for #97997

Assembly diffs

Assembly diffs for osx/arm64 ran on windows/x64

Diffs are based on 1,731,165 contexts (559,643 MinOpts, 1,171,522 FullOpts).

MISSED contexts: base: 227 (0.01%), diff: 5,356 (0.31%)

Overall (-12 bytes)
Collection Base size (bytes) Diff size (bytes)
libraries.pmi.osx.arm64.checked.mch 79,814,192 -12
FullOpts (-12 bytes)
Collection Base size (bytes) Diff size (bytes)
libraries.pmi.osx.arm64.checked.mch 79,693,064 -12

Details here


Throughput diffs

Throughput diffs for windows/arm64 ran on windows/x64

MinOpts (-0.00% to +0.01%)
Collection PDIFF
libraries.pmi.windows.arm64.checked.mch +0.01%

Details here


@VSadov
Copy link
Member

VSadov commented Feb 26, 2024

Just curious - what are the savings here? Pinning references that happen to be stack refs seems like mostly a noop - basically just a different bit in GCinfo with no effect on GC. Once GC figures that byref does not point to the heap (which it needs to figure regardless), it will not care about pinning. Actually will stop caring about the reference entirely.

Are there some possible savings on the JIT side?

@MichalPetryka
Copy link
Contributor Author

Just curious - what are the savings here? Pinning references that happen to be stack refs seems like mostly a noop - basically just a different bit in GCinfo with no effect on GC. Once GC figures that byref does not point to the heap (which it needs to figure regardless), it will not care about pinning. Actually will stop caring about the reference entirely.

Are there some possible savings on the JIT side?

The motivation was to improve the codegen a bit as some people use such pinning to workaround the lack of Unsafe for ref structs.

Example:
image
Before:
image
After:
image

@VSadov
Copy link
Member

VSadov commented Feb 27, 2024

The motivation was to improve the codegen a bit

I see. That is basically because pinned locals are currently never enregistered, so pinning will force a local to the stack.
I kind of suspected that, just was not sure if it can cause a real grief to people or perhaps it is something else.

@Sergio0694
Copy link
Contributor

Does this also ignores pinning for fields of ref struct types (pointed to by ref)? Eg.:

fixed (long* p = &r2.a)
{
}

(using the same types and names as that snippet in #97997 (comment))

@MichalPetryka
Copy link
Contributor Author

Does this also ignores pinning for fields of ref struct types (pointed to by ref)? Eg.:

fixed (long* p = &r2.a)
{
}

(using the same types and names as that snippet in #97997 (comment))

No as the local is not a ref to a ref struct in such case.

@Sergio0694
Copy link
Contributor

Not sure I'm following. What I meant is, if you have:

ref struct MyRefStruct
{
    public int X;
}

void Test(ref MyRefStruct value)
{
    fixed (int* p = &value.X)
    {
        DoStuff(p);
    }
}

Shouldn't it also be possible to ignore the pinning on that X field? 🤔

@MichalPetryka
Copy link
Contributor Author

Not sure I'm following. What I meant is, if you have:

ref struct MyRefStruct
{
    public int X;
}

void Test(ref MyRefStruct value)
{
    fixed (int* p = &value.X)
    {
        DoStuff(p);
    }
}

Shouldn't it also be possible to ignore the pinning on that X field? 🤔

The optimization here is done before the JIT even looks at the IL opcodes so it doesn't know what's assigned to the local, the case you've mentioned would need to be handled later on by looking at the struct layout the FIELD_ADDR for X is coming from and checking if the type there is a ref struct. I think it should be possible but I'm not 100% sure if "unpinning" the local would have any issues then.

@jakobbotsch
Copy link
Member

Unfortunately I don't think the impact of the optimization warrants us changing the model of what kind of byref aliasing we consider legal within the JIT. If we could demonstrate some larger impact, then I wouldn't be against reconsidering. Thanks!

@github-actions github-actions bot locked and limited conversation to collaborators Apr 25, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants