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

Intrinsify typeof(T).IsGenericType #97148

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/coreclr/jit/fgbasic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,7 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed
case NI_System_Type_get_IsValueType:
case NI_System_Type_get_IsPrimitive:
case NI_System_Type_get_IsByRefLike:
case NI_System_Type_get_IsGenericType:
case NI_System_Type_GetTypeFromHandle:
case NI_System_String_get_Length:
case NI_System_Buffers_Binary_BinaryPrimitives_ReverseEndianness:
Expand Down
27 changes: 27 additions & 0 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2726,6 +2726,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
case NI_System_Type_get_IsByRefLike:
case NI_System_Type_IsAssignableFrom:
case NI_System_Type_IsAssignableTo:
case NI_System_Type_get_IsGenericType:

// Lightweight intrinsics
case NI_System_String_get_Chars:
Expand Down Expand Up @@ -3318,6 +3319,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
case NI_System_Type_get_IsValueType:
case NI_System_Type_get_IsPrimitive:
case NI_System_Type_get_IsByRefLike:
case NI_System_Type_get_IsGenericType:
{
// Optimize
//
Expand Down Expand Up @@ -3364,6 +3366,27 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
retNode = gtNewFalse();
}
break;
case NI_System_Type_get_IsGenericType:
// a type is a generic type if there is at least one type argument that is
// some valid type, so we can always just check the one at index 0 for this.
// This will work on open generic types as well, and the returned type handle
// will be some handle that represents the (non constructed) type parameter.
if (info.compCompHnd->getTypeInstantiationArgument(hClass, 0) != NO_CLASS_HANDLE)
{
retNode = gtNewTrue();
}
else if ((info.compCompHnd->getClassAttribs(hClass) & CORINFO_FLG_SHAREDINST) != 0)
{
// if we have no type arguments, check that the type itself is not __Canon, and
Copy link
Member

Choose a reason for hiding this comment

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

You will also get here for __Canon[] that it is perfectly fine to hardcode false for.

I understand that you are trying to reuse the existing JIT/EE interface APIs and flags in creative ways, but doing so often leads to bugs like #97134 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. I assume for the same reason we shouldn't just try to check for well known cases here, like excluding arrays and pointers types? If we should add a new JIT/EE method for this, what other VM method should it be using instead, is there one that already does what we want to do here? 🤔

Copy link
Member

Choose a reason for hiding this comment

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

Yes, introducing JIT/EE interface method that answers the exact question tends to work the best. It can be similar to the existing isEnum method.

// simply skip expanding the intrinsic in that case, rather than incorrectly
// hardcoding 'false' as the resulting expression.
retNode = nullptr;
}
else
{
retNode = gtNewFalse();
}
break;

default:
NO_WAY("Intrinsic not supported in this path.");
Expand Down Expand Up @@ -9006,6 +9029,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
{
result = NI_System_Type_get_IsPrimitive;
}
else if (strcmp(methodName, "get_IsGenericType") == 0)
{
result = NI_System_Type_get_IsGenericType;
}
else if (strcmp(methodName, "get_IsByRefLike") == 0)
{
result = NI_System_Type_get_IsByRefLike;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ enum NamedIntrinsic : unsigned short
NI_System_Type_get_IsPrimitive,
NI_System_Type_get_IsByRefLike,
NI_System_Type_get_TypeHandle,
NI_System_Type_get_IsGenericType,
NI_System_Type_IsAssignableFrom,
NI_System_Type_IsAssignableTo,
NI_System_Type_op_Equality,
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Private.CoreLib/src/System/Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public bool IsInterface
public virtual bool IsGenericParameter => false;
public virtual bool IsGenericTypeParameter => IsGenericParameter && DeclaringMethod is null;
public virtual bool IsGenericMethodParameter => IsGenericParameter && DeclaringMethod != null;
public virtual bool IsGenericType => false;
public virtual bool IsGenericType { [Intrinsic] get => false; }
public virtual bool IsGenericTypeDefinition => false;

public virtual bool IsSZArray => throw NotImplemented.ByDesign;
Expand Down
79 changes: 79 additions & 0 deletions src/tests/JIT/Intrinsics/TypeIntrinsics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public static int TestEntryPoint()
GetEnumUnderlyingType.TestGetEnumUnderlyingType();

IsPrimitiveTests();
IsGenericTypeTests();

return 100 + _errors;
}
Expand Down Expand Up @@ -186,6 +187,73 @@ private static void IsPrimitiveTests()
IsFalse(typeof(Dictionary<,>).IsPrimitive);
}

private static void IsGenericTypeTests()
{
IsFalse(typeof(bool).IsGenericType);
IsFalse(typeof(char).IsGenericType);
IsFalse(typeof(sbyte).IsGenericType);
IsFalse(typeof(byte).IsGenericType);
IsFalse(typeof(short).IsGenericType);
IsFalse(typeof(ushort).IsGenericType);
IsFalse(typeof(int).IsGenericType);
IsFalse(typeof(uint).IsGenericType);
IsFalse(typeof(long).IsGenericType);
IsFalse(typeof(ulong).IsGenericType);
IsFalse(typeof(float).IsGenericType);
IsFalse(typeof(double).IsGenericType);
IsFalse(typeof(nint).IsGenericType);
IsFalse(typeof(nuint).IsGenericType);
IsFalse(typeof(IntPtr).IsGenericType);
IsFalse(typeof(UIntPtr).IsGenericType);
IsFalse(typeof(Enum).IsGenericType);
IsFalse(typeof(ValueType).IsGenericType);
IsFalse(typeof(SimpleEnum).IsGenericType);
IsFalse(typeof(IntPtrEnum).IsGenericType);
IsFalse(typeof(FloatEnum).IsGenericType);
IsFalse(typeof(decimal).IsGenericType);
IsFalse(typeof(TimeSpan).IsGenericType);
IsFalse(typeof(DateTime).IsGenericType);
IsFalse(typeof(DateTimeOffset).IsGenericType);
IsFalse(typeof(Guid).IsGenericType);
IsFalse(typeof(Half).IsGenericType);
IsFalse(typeof(DateOnly).IsGenericType);
IsFalse(typeof(TimeOnly).IsGenericType);
IsFalse(typeof(Int128).IsGenericType);
IsFalse(typeof(UInt128).IsGenericType);
IsFalse(typeof(string).IsGenericType);
IsFalse(typeof(object).IsGenericType);
IsFalse(typeof(RuntimeArgumentHandle).IsGenericType);
IsFalse(typeof(DerivedGenericSimpleClass).IsGenericType);
IsFalse(typeof(int[]).IsGenericType);
IsFalse(typeof(int[,]).IsGenericType);
IsFalse(typeof(int*).IsGenericType);
IsFalse(typeof(void*).IsGenericType);
IsFalse(typeof(delegate*<int>).IsGenericType);

IsTrue(typeof(GenericSimpleClass<int>).IsGenericType);
IsTrue(typeof(GenericSimpleClass<>).IsGenericType);
IsTrue(typeof(GenericSimpleClass<int>.Nested).IsGenericType);
IsTrue(typeof(GenericSimpleClass<>.Nested).IsGenericType);
IsTrue(typeof(GenericEnumClass<SimpleEnum>).IsGenericType);
IsTrue(typeof(GenericEnumClass<>).IsGenericType);
IsTrue(typeof(IGenericInterface<string>).IsGenericType);
IsTrue(typeof(IGenericInterface<>).IsGenericType);
IsTrue(typeof(GenericStruct<string>).IsGenericType);
IsTrue(typeof(GenericStruct<>).IsGenericType);
IsTrue(typeof(SimpleEnum?).IsGenericType);
IsTrue(typeof(int?).IsGenericType);
IsTrue(typeof(IntPtr?).IsGenericType);
IsTrue(typeof(Nullable<>).IsGenericType);
IsTrue(typeof(Dictionary<int,string>).IsGenericType);
IsTrue(typeof(Dictionary<,>).IsGenericType);
IsTrue(typeof(List<string>).IsGenericType);
IsTrue(typeof(List<>).IsGenericType);
IsTrue(typeof(Action<>).IsGenericType);
IsTrue(typeof(Action<string>).IsGenericType);
IsTrue(typeof(Func<string, int>).IsGenericType);
IsTrue(typeof(Func<,>).IsGenericType);
}

private static int _varInt = 42;
private static int? _varNullableInt = 42;
private static decimal _varDecimal = 42M;
Expand Down Expand Up @@ -258,6 +326,17 @@ static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0, [CallerFil
}
}

public class GenericSimpleClass<T>
{
public class Nested
{
}
}

public class DerivedGenericSimpleClass : GenericSimpleClass<string>
{
}

public class GenericEnumClass<T> where T : Enum
{
public T field;
Expand Down
Loading