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

Generic method constrained by struct & interface can not see readonly modifier #76355

Closed
bollhals opened this issue Sep 29, 2022 · 7 comments
Closed
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI tenet-performance Performance related issue

Comments

@bollhals
Copy link
Contributor

Description

If you have a generic method with a struct & interface constrain and that interface contains a method, it is not able to see that the method implemented in the struct is readonly, so it ends up creating defensive copies even though it shouldn't need to.

Configuration

Any version, even on main

Regression?

No.

Data

Example on SharpLab:

using System;
using SharpLab.Runtime;
using System.Runtime.CompilerServices;

public class C {
    [JitGeneric(typeof(S))]
    public void MIn<T>(in T s)
        where T : struct, IInterface
    {
            s.Test();
    }
    
    [JitGeneric(typeof(S))]
    public void MRef<T>(ref T s)
        where T : struct, IInterface
    {
            s.Test();
    }
}

public interface IInterface {
    void Test();
}

public readonly struct S : IInterface
{
    public readonly long P1;
    public readonly long P2;
    public readonly long P3;
    public readonly long P4;
    
    [MethodImpl(MethodImplOptions.NoInlining)]
    public readonly void Test()
    {
    }
}
C.MIn[[S, _]](S ByRef)
    L0000: sub esp, 0x20
    L0003: vzeroupper
    L0006: vmovdqu xmm0, [edx]
    L000a: vmovdqu [esp], xmm0
    L000f: vmovdqu xmm0, [edx+0x10]
    L0014: vmovdqu [esp+0x10], xmm0
    L001a: lea ecx, [esp]
    L001d: call dword ptr [0xf51ccd0]
    L0023: add esp, 0x20
    L0026: ret

C.MRef[[S, _]](S ByRef)
    L0000: mov ecx, edx
    L0002: call dword ptr [0xf51ccd0]
    L0008: ret

@bollhals bollhals added the tenet-performance Performance related issue label Sep 29, 2022
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Sep 29, 2022
@marek-safar marek-safar added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Sep 29, 2022
@ghost
Copy link

ghost commented Sep 29, 2022

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

Issue Details

Description

If you have a generic method with a struct & interface constrain and that interface contains a method, it is not able to see that the method implemented in the struct is readonly, so it ends up creating defensive copies even though it shouldn't need to.

Configuration

Any version, even on main

Regression?

No.

Data

Example on SharpLab:

using System;
using SharpLab.Runtime;
using System.Runtime.CompilerServices;

public class C {
    [JitGeneric(typeof(S))]
    public void MIn<T>(in T s)
        where T : struct, IInterface
    {
            s.Test();
    }
    
    [JitGeneric(typeof(S))]
    public void MRef<T>(ref T s)
        where T : struct, IInterface
    {
            s.Test();
    }
}

public interface IInterface {
    void Test();
}

public readonly struct S : IInterface
{
    public readonly long P1;
    public readonly long P2;
    public readonly long P3;
    public readonly long P4;
    
    [MethodImpl(MethodImplOptions.NoInlining)]
    public readonly void Test()
    {
    }
}
C.MIn[[S, _]](S ByRef)
    L0000: sub esp, 0x20
    L0003: vzeroupper
    L0006: vmovdqu xmm0, [edx]
    L000a: vmovdqu [esp], xmm0
    L000f: vmovdqu xmm0, [edx+0x10]
    L0014: vmovdqu [esp+0x10], xmm0
    L001a: lea ecx, [esp]
    L001d: call dword ptr [0xf51ccd0]
    L0023: add esp, 0x20
    L0026: ret

C.MRef[[S, _]](S ByRef)
    L0000: mov ecx, edx
    L0002: call dword ptr [0xf51ccd0]
    L0008: ret

Author: bollhals
Assignees: -
Labels:

tenet-performance, area-CodeGen-coreclr, untriaged

Milestone: -

@tfenise
Copy link
Contributor

tfenise commented Sep 29, 2022

This is rather a problem with C#. The IL already includes codes to create a defensive copy, as the C# compiler does not know whether T is readonly, so it generates codes according to the worst case. struct being readonly is only a concept of C#, not the runtime. You could argue that maybe the C# feature should have better support from the runtime, though.

@tfenise
Copy link
Contributor

tfenise commented Sep 29, 2022

See also dotnet/csharplang#3055.

@bollhals
Copy link
Contributor Author

Ah, there was that issue that I once saw ... I know I read it somewhere, but I couldn't find it anymore. Thanks for linking it.

@jakobbotsch
Copy link
Member

I don't think there is much the JIT can do here. From the IL it sees the proposed optimization would be illegal -- it would change output of a program such as the following:

public class C
{
    public void MIn<T>(in T s)
        where T : struct, IInterface
    {
        s.Test();
    }

    public void MRef<T>(ref T s)
        where T : struct, IInterface
    {
        s.Test();
    }

    public unsafe static void Main()
    {
        S s = new S();
        S.F = &s;
        new C().MIn(in s);
    }
}

public interface IInterface
{
    void Test();
}

public unsafe readonly struct S : IInterface
{
    public readonly long P1;
    public readonly long P2;
    public readonly long P3;
    public readonly long P4;

    [MethodImpl(MethodImplOptions.NoInlining)]
    public readonly void Test()
    {
        fixed (S* pThis = &this)
            Console.WriteLine(pThis == F);
    }

    public static S* F;
}

@JulieLeeMSFT
Copy link
Member

Closing the issue. @bollhals, please reopen if you still think that JIT has to change something.

I don't think there is much the JIT can do here. From the IL it sees the proposed optimization would be illegal -- it would change output of a program such as the following:

@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Sep 30, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Oct 30, 2022
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 tenet-performance Performance related issue
Projects
None yet
Development

No branches or pull requests

5 participants