ashmind / SharpLab

.NET language playground
https://sharplab.io
BSD 2-Clause "Simplified" License
2.72k stars 199 forks source link

JitGenericAttribute doesn't work if arguments are reference types #99

Open ashmind opened 7 years ago

JosephTremoulet commented 7 years ago

I'd guess this has to do with "generic sharing" - for reference types, we generate a single method body of jitted machine code and use that for all of them (it has extra indirections for things that depend on the specific reference type, like if the code has new T()). There's a special placeholder type called System. Canon that represents "some unspecified reference type" which is what the VM tells the JIT is the type argument that the generic is being instantiated with in these cases. So I wouldn't be surprised if you're referencing a specific reference type in some api call where you need to be substituting System. Canon instead. But I'm not familiar enough with these interfaces to know which might be having the issue or how to reference System. Canon in it...

(all of this is just a guess based on the statement that reference types are what's failing, btw)

ashmind commented 7 years ago

@JosephTremoulet Thanks! I actually considered that and tried to use MakeGenericType with System.__Canon, but I wasn't able to get it to work either. My current guess is that clrmd resolves shared generics incorrectly, but sadly that will require significant time to research/debug.

ashmind commented 7 years ago

The problem here is that HotSize is 0 btw (no cold either). I saw same issue with some other generics (inherited/non-open) where the same method was duplicated on a type -- once without proper HotColdInfo and once with it. I was able to resolve it by finding a correct duplicate. Might be related, but I can't apply the same solution here.

JosephTremoulet commented 7 years ago

Maybe you could try using the approach outlined here and see if it gets better results than using HotColdInfo.

ashmind commented 5 years ago

This works in .NET Core branch, so closing as "cannot reproduce".

canton7 commented 4 years ago

Just saw this message pop up, and noticed this issue was "cannot reproduce", so sharing:

using System;
using SharpLab.Runtime;
public class C {
    [JitGeneric(typeof(int)), JitGeneric(typeof(string))]
    public string M<T>() {
        if (typeof(T) == typeof(int))
            return "int";
        if (typeof(T) == typeof(string))
            return "string";
        return "neither";
    }
}

Compile as x64 (or Default/master), Release. Link.

mstefarov commented 4 years ago

Another reproducer here.

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

public static class CompareDynamicCasting {
    [JitGeneric(typeof(int)), JitGeneric(typeof(string))]
    public static T UseUnsafeAs<T>()
    {
        if (typeof(T) == typeof(string))
        {
            var myStr = MakeString();
            return Unsafe.As<string, T>(ref myStr);
        }else if (typeof(T) == typeof(int)){
            int myInt = MakeNumber();
            return Unsafe.As<int, T>(ref myInt);
        }else{
            return default;
        }
    }

    [JitGeneric(typeof(int)), JitGeneric(typeof(string))]
    public static T UseChangeType<T>()
    {
        if (typeof(T) == typeof(string))
        {
            var myStr = MakeString();
            return (T)Convert.ChangeType(myStr, typeof(T));
        }else if (typeof(T) == typeof(int)){
            int myInt = MakeNumber();
            return (T)Convert.ChangeType(myInt, typeof(T));
        }else{
            return default;
        }
    }

    [MethodImplAttribute(MethodImplOptions.NoInlining)] 
    private static string MakeString() => "boo";

    [MethodImplAttribute(MethodImplOptions.NoInlining)] 
    private static int MakeNumber() => 1;
}

Viewing JIT Asm works for int but not for string.

ashmind commented 4 years ago

Thanks! I'll revisit it when I have a moment.

kkokosa commented 4 years ago

Yep, simple case:

using System;
using SharpLab.Runtime;

public class C {}
public struct S {}
public class Program
{
    [JitGeneric(typeof(C)), JitGeneric(typeof(S))]
    public bool IsValueType<T>() => typeof(T).IsValueType;
}
kikaragyozov commented 3 years ago

Is there any way we can view JIT compiled code on our own (in Release)? That way we'll at least be able to see what is actually being generated when we define a generic that accepts reference typed arguments.

ashmind commented 3 years ago

Just FYI, asked for some more clarification in https://github.com/dotnet/runtime/discussions/56274.

rootflood commented 3 years ago

i use MethodImplOptions.AggressiveInlining flag for this case sharplab

cathei commented 2 years ago

If anyone needs workaround, you can use a generic class instead of generic method. For example while this one is not working:

public class C {}
public struct S {}

public class Program
{
    [JitGeneric(typeof(C)), JitGeneric(typeof(S))]
    public bool IsValueType<T>() => typeof(T).IsValueType;
}

Convert to this will work:

public class C {}
public struct S {}

public class Program
{
    [JitGeneric(typeof(C)), JitGeneric(typeof(S))]
    public class Wrapper<T>
    {
        public bool IsValueType() => typeof(T).IsValueType;
    }
}
timcassell commented 1 year ago

I'm also seeing this issue with a generic struct containing a reference type.

[JitGeneric(typeof(KeyValuePair<int, object>))]