Skip to content
Merged
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
54 changes: 49 additions & 5 deletions src/coreclr/src/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3447,8 +3447,8 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,

if ((methodFlags & CORINFO_FLG_JIT_INTRINSIC) != 0)
{
// The recursive calls to Jit intrinsics are must-expand by convention.
mustExpand = mustExpand || gtIsRecursiveCall(method);
// The recursive non-virtual calls to Jit intrinsics are must-expand by convention.
mustExpand = mustExpand || (gtIsRecursiveCall(method) && !(methodFlags & CORINFO_FLG_VIRTUAL));

if (intrinsicID == CORINFO_INTRINSIC_Illegal)
{
Expand Down Expand Up @@ -4014,6 +4014,49 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
break;
}

case NI_System_Type_IsAssignableFrom:
{
// Optimize patterns like:
//
// typeof(TTo).IsAssignableFrom(typeof(TTFrom))
// valueTypeVar.GetType().IsAssignableFrom(typeof(TTFrom))
//
// to true/false
GenTree* typeTo = impStackTop(1).val;
GenTree* typeFrom = impStackTop(0).val;

if (typeTo->IsCall() && typeFrom->IsCall())
{
// make sure both arguments are `typeof()`
Copy link
Member

Choose a reason for hiding this comment

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

Do we know what typical uses of IsAssignableFrom look like? Are they usually via typeof or does GetType show up with any frequency?

Copy link
Member Author

@EgorBo EgorBo Dec 28, 2019

Choose a reason for hiding this comment

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

Good point, I started with typeof()+typeof() but it seems it makes sense to handle more cases (e.g. a.GetType().IsAssignableFrom(b.GetType())) will analyze usages in popular projects.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

@EgorBo EgorBo Dec 28, 2019

Choose a reason for hiding this comment

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

Currently this code handles two cases:

bool a = typeof(T1).IsAssignableFrom(typeof(T2));

bool a = variable.GetType().IsAssignableFrom(typeof(T2)); // for ValueType variables

CORINFO_METHOD_HANDLE hTypeof = eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE);
if ((typeTo->AsCall()->gtCallMethHnd == hTypeof) && (typeFrom->AsCall()->gtCallMethHnd == hTypeof))
{
CORINFO_CLASS_HANDLE hClassTo =
gtGetHelperArgClassHandle(typeTo->AsCall()->gtCallArgs->GetNode());
CORINFO_CLASS_HANDLE hClassFrom =
gtGetHelperArgClassHandle(typeFrom->AsCall()->gtCallArgs->GetNode());

if (hClassTo == NO_CLASS_HANDLE || hClassFrom == NO_CLASS_HANDLE)
{
break;
}

TypeCompareState castResult = info.compCompHnd->compareTypesForCast(hClassFrom, hClassTo);
if (castResult == TypeCompareState::May)
{
// requires runtime check
// e.g. __Canon, COMObjects, Nullable
break;
}

retNode = gtNewIconNode((castResult == TypeCompareState::Must) ? 1 : 0);
impPopStack(); // drop both CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE calls
impPopStack();
}
}
break;
}

case NI_System_Type_get_IsValueType:
{
// Optimize
Expand Down Expand Up @@ -4341,6 +4384,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
{
result = NI_System_Type_get_IsValueType;
}
else if (strcmp(methodName, "IsAssignableFrom") == 0)
{
result = NI_System_Type_IsAssignableFrom;
}
}
}
#if defined(TARGET_XARCH) // We currently only support BSWAP on x86
Expand Down Expand Up @@ -7603,9 +7650,6 @@ var_types Compiler::impImportCall(OPCODE opcode,

if (call != nullptr)
{
assert(!(mflags & CORINFO_FLG_VIRTUAL) || (mflags & CORINFO_FLG_FINAL) ||
(clsFlags & CORINFO_FLG_FINAL));

#ifdef FEATURE_READYTORUN_COMPILER
if (call->OperGet() == GT_INTRINSIC)
{
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum NamedIntrinsic : unsigned short
NI_System_Buffers_Binary_BinaryPrimitives_ReverseEndianness,
NI_System_GC_KeepAlive,
NI_System_Type_get_IsValueType,
NI_System_Type_IsAssignableFrom,

#ifdef FEATURE_HW_INTRINSICS
NI_IsSupported_True,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;

public partial class Program
{
public static void TestIsAssignableFrom()
{
// Primitive types
IsTrue (typeof(void).IsAssignableFrom(typeof(void)));
IsTrue (typeof(byte).IsAssignableFrom(typeof(byte)));
IsTrue (typeof(int).IsAssignableFrom(typeof(int)));
IsTrue (typeof(float).IsAssignableFrom(typeof(float)));
IsTrue (typeof(double).IsAssignableFrom(typeof(double)));
IsTrue (typeof(byte*).IsAssignableFrom(typeof(byte*)));
IsTrue (typeof(sbyte*).IsAssignableFrom(typeof(byte*)));
IsTrue (typeof(void*).IsAssignableFrom(typeof(void*)));
IsTrue (typeof(byte**).IsAssignableFrom(typeof(byte**)));
IsFalse(typeof(byte).IsAssignableFrom(typeof(sbyte)));
IsFalse(typeof(sbyte).IsAssignableFrom(typeof(byte)));
IsFalse(typeof(int).IsAssignableFrom(typeof(long)));
IsFalse(typeof(int).IsAssignableFrom(typeof(void)));
IsFalse(typeof(void).IsAssignableFrom(typeof(long)));
IsFalse(typeof(long).IsAssignableFrom(typeof(int)));
IsFalse(typeof(float).IsAssignableFrom(typeof(double)));
IsFalse(typeof(double).IsAssignableFrom(typeof(float)));
IsFalse(typeof(double).IsAssignableFrom(typeof(long)));
IsFalse(typeof(int).IsAssignableFrom(typeof(float)));
IsFalse(typeof(sbyte*).IsAssignableFrom(typeof(ulong*)));
IsFalse(typeof(sbyte*).IsAssignableFrom(typeof(void*)));
IsFalse(typeof(void*).IsAssignableFrom(typeof(ulong*)));
IsFalse(typeof(sbyte*).IsAssignableFrom(typeof(IntPtr)));
IsFalse(typeof(IntPtr).IsAssignableFrom(typeof(sbyte*)));
IsFalse(typeof(byte**).IsAssignableFrom(typeof(byte*)));
IsFalse(typeof(byte*).IsAssignableFrom(typeof(byte**)));

// Nullable
IsTrue (typeof(int?).IsAssignableFrom(typeof(int)));
IsTrue (typeof(int?).IsAssignableFrom(typeof(int?)));
IsTrue (typeof(GenericStruct1<int>?).IsAssignableFrom(typeof(GenericStruct1<int>?)));
IsTrue (typeof(GenericStruct1<string>?).IsAssignableFrom(typeof(GenericStruct1<string>?)));
IsTrue (typeof(SimpleEnum_int?).IsAssignableFrom(typeof(SimpleEnum_int?)));
IsFalse(typeof(int).IsAssignableFrom(typeof(int?)));
IsFalse(typeof(uint?).IsAssignableFrom(typeof(int?)));
IsFalse(typeof(int?).IsAssignableFrom(typeof(uint?)));
IsFalse(typeof(SimpleEnum_uint?).IsAssignableFrom(typeof(SimpleEnum_int?)));
IsFalse(typeof(SimpleEnum_int?).IsAssignableFrom(typeof(SimpleEnum_uint?)));
IsFalse(typeof(GenericStruct1<int>?).IsAssignableFrom(typeof(GenericStruct1<uint>?)));

// Enums
IsTrue (typeof(SimpleEnum_int).IsAssignableFrom(typeof(SimpleEnum_int)));
IsTrue (typeof(SimpleEnum_int?).IsAssignableFrom(typeof(SimpleEnum_int?)));
IsTrue (typeof(SimpleEnum_uint).IsAssignableFrom(typeof(SimpleEnum_uint)));
IsTrue (typeof(SimpleEnum_byte).IsAssignableFrom(typeof(SimpleEnum_byte)));
IsTrue (typeof(ValueType).IsAssignableFrom(typeof(SimpleEnum_uint)));
IsFalse(typeof(SimpleEnum_int).IsAssignableFrom(typeof(SimpleEnum_uint)));
IsFalse(typeof(SimpleEnum_uint).IsAssignableFrom(typeof(SimpleEnum_int)));
IsFalse(typeof(SimpleEnum_uint).IsAssignableFrom(typeof(SimpleEnum_byte)));
IsFalse(typeof(SimpleEnum_byte).IsAssignableFrom(typeof(SimpleEnum_uint)));
IsFalse(typeof(SimpleEnum_byte).IsAssignableFrom(typeof(byte)));
IsFalse(typeof(byte).IsAssignableFrom(typeof(SimpleEnum_byte)));
IsFalse(typeof(SimpleEnum_int).IsAssignableFrom(typeof(int)));
IsFalse(typeof(int).IsAssignableFrom(typeof(SimpleEnum_int)));
IsFalse(typeof(SimpleEnum_uint).IsAssignableFrom(typeof(float)));
IsFalse(typeof(float).IsAssignableFrom(typeof(SimpleEnum_uint)));
IsFalse(typeof(SimpleEnum_uint).IsAssignableFrom(typeof(ValueType)));

// Covariance/Contravariance
IsTrue (typeof(IEnumerable<object>).IsAssignableFrom(typeof(List<string>)));
IsTrue (typeof(IEnumerable<ClassA>).IsAssignableFrom(typeof(List<ClassB>)));
IsTrue (typeof(IEnumerable<ClassA>).IsAssignableFrom(typeof(IList<ClassB>)));
IsTrue (typeof(IEnumerable<ClassA>).IsAssignableFrom(typeof(IList<ClassD>)));
IsTrue (typeof(IEnumerable<ClassA>).IsAssignableFrom(typeof(IList<ClassA>)));
IsTrue (typeof(Action<string>).IsAssignableFrom(typeof(Action<object>)));
IsTrue (typeof(object[]).IsAssignableFrom(typeof(string[])));
IsTrue (typeof(object[,]).IsAssignableFrom(typeof(string[,])));
IsTrue (typeof(SimpleEnum_int[,]).IsAssignableFrom(typeof(SimpleEnum_uint[,])));
IsFalse(typeof(string[,]).IsAssignableFrom(typeof(object[,])));
IsFalse(typeof(object[,]).IsAssignableFrom(typeof(string[,,])));
IsFalse(typeof(IDictionary<ClassA, int>).IsAssignableFrom(typeof(IDictionary<ClassB, int>)));
IsFalse(typeof(IDictionary<ClassA, int>).IsAssignableFrom(typeof(Dictionary<ClassB, int>)));
IsFalse(typeof(Action<object>).IsAssignableFrom(typeof(Action<string>)));
IsFalse(typeof(Action<object>).IsAssignableFrom(typeof(Action<Guid>)));
IsFalse(typeof(List<string>).IsAssignableFrom(typeof(IEnumerable<object>)));
IsFalse(typeof(Action<Guid>).IsAssignableFrom(typeof(Action<object>)));
IsFalse(typeof(List<ClassB>).IsAssignableFrom(typeof(IEnumerable<ClassA>)));
IsFalse(typeof(IList<ClassB>).IsAssignableFrom(typeof(IEnumerable<ClassA>)));
IsFalse(typeof(IList<ClassD>).IsAssignableFrom(typeof(IEnumerable<ClassA>)));
IsFalse(typeof(IList<ClassA>).IsAssignableFrom(typeof(IEnumerable<ClassA>)));

// Arrays
IsTrue(typeof(byte[]).IsAssignableFrom(typeof(sbyte[])));
IsTrue(typeof(sbyte[]).IsAssignableFrom(typeof(byte[])));
IsTrue(typeof(short[]).IsAssignableFrom(typeof(ushort[])));
IsTrue(typeof(ushort[]).IsAssignableFrom(typeof(short[])));
IsTrue(typeof(int[]).IsAssignableFrom(typeof(uint[])));
IsTrue(typeof(uint[]).IsAssignableFrom(typeof(int[])));
IsTrue(typeof(long[]).IsAssignableFrom(typeof(ulong[])));
IsTrue(typeof(ulong[]).IsAssignableFrom(typeof(long[])));
IsTrue(typeof(long[,]).IsAssignableFrom(typeof(ulong[,])));
IsTrue(typeof(ulong[,,]).IsAssignableFrom(typeof(long[,,])));
IsTrue(typeof(Struct1[]).IsAssignableFrom(typeof(Struct1[])));
IsFalse(typeof(int[]).IsAssignableFrom(typeof(byte[])));
IsFalse(typeof(int[]).IsAssignableFrom(typeof(sbyte[])));
IsFalse(typeof(int[]).IsAssignableFrom(typeof(short[])));
IsFalse(typeof(int[]).IsAssignableFrom(typeof(ushort[])));
IsFalse(typeof(int[]).IsAssignableFrom(typeof(float[])));
IsFalse(typeof(int[]).IsAssignableFrom(typeof(double[])));
IsFalse(typeof(long[]).IsAssignableFrom(typeof(double[])));
IsFalse(typeof(Struct1[]).IsAssignableFrom(typeof(Struct2[])));
IsFalse(typeof(Struct1[]).IsAssignableFrom(typeof(GenericStruct1<int>[])));
IsFalse(typeof(GenericStruct1<uint>[]).IsAssignableFrom(typeof(GenericStruct1<int>[])));

// Misc
IsTrue (typeof(object).IsAssignableFrom(typeof(byte)));
IsTrue (typeof(object).IsAssignableFrom(typeof(int)));
IsTrue (typeof(object).IsAssignableFrom(typeof(float)));
IsTrue (typeof(object).IsAssignableFrom(typeof(SimpleEnum_uint)));
IsTrue (typeof(object).IsAssignableFrom(typeof(IDisposable)));
IsTrue (typeof(object).IsAssignableFrom(typeof(IDictionary<string, string>)));
IsTrue (typeof(object).IsAssignableFrom(typeof(List<int>)));
IsTrue (typeof(object).IsAssignableFrom(typeof(List<>)));
IsTrue (typeof(object).IsAssignableFrom(typeof(Action<>)));
IsTrue (typeof(object).IsAssignableFrom(typeof(Action<int>)));
IsTrue (typeof(object).IsAssignableFrom(typeof(Vector128<float>)));
IsTrue (typeof(object).IsAssignableFrom(typeof(Vector256<int>)));
IsTrue (typeof(ClassA).IsAssignableFrom(typeof(ClassA)));
IsTrue (typeof(ClassA).IsAssignableFrom(typeof(ClassB)));
IsTrue (typeof(ClassA).IsAssignableFrom(typeof(ClassC)));
IsTrue (typeof(decimal).IsAssignableFrom(typeof(decimal)));
IsTrue (typeof(Struct1).IsAssignableFrom(typeof(Struct1)));
IsTrue (typeof(IDisposable).IsAssignableFrom(typeof(Struct3)));
IsTrue (typeof(Dictionary<,>).IsAssignableFrom(typeof(Dictionary<,>)));
IsTrue (typeof(IDictionary<,>).IsAssignableFrom(typeof(IDictionary<,>)));
IsTrue (typeof(GenericStruct1<>).IsAssignableFrom(typeof(GenericStruct1<>)));
IsTrue (typeof(GenericStruct1<int>).IsAssignableFrom(typeof(GenericStruct1<int>)));
IsTrue (typeof(GenericStruct1<string>).IsAssignableFrom(typeof(GenericStruct1<string>)));
IsFalse(typeof(byte).IsAssignableFrom(typeof(IDisposable)));
IsFalse(typeof(IDisposable).IsAssignableFrom(typeof(IEnumerable)));
IsFalse(typeof(IDictionary<string, string>).IsAssignableFrom(typeof(IDictionary<string, int>)));
IsFalse(typeof(List<int>).IsAssignableFrom(typeof(IList<int>)));
IsFalse(typeof(List<>).IsAssignableFrom(typeof(List<IDisposable>)));
IsFalse(typeof(Action<>).IsAssignableFrom(typeof(Action<int>)));
IsFalse(typeof(Action<>).IsAssignableFrom(typeof(Func<int>)));
IsFalse(typeof(Action).IsAssignableFrom(typeof(CustomAction)));
IsFalse(typeof(Action<int>).IsAssignableFrom(typeof(void)));
IsFalse(typeof(ClassB).IsAssignableFrom(typeof(ClassD)));
IsFalse(typeof(Dictionary<,>).IsAssignableFrom(typeof(Dictionary<int,int>)));
IsFalse(typeof(GenericStruct1<ClassA>).IsAssignableFrom(typeof(GenericStruct1<ClassB>)));
IsFalse(typeof(Struct1).IsAssignableFrom(typeof(Struct2)));
IsFalse(typeof(GenericStruct1<>).IsAssignableFrom(typeof(GenericStruct2<>)));
IsFalse(typeof(GenericStruct1<int>).IsAssignableFrom(typeof(GenericStruct2<int>)));
IsFalse(typeof(object).IsAssignableFrom(typeof(byte*)));
IsFalse(typeof(object).IsAssignableFrom(typeof(byte**)));
IsFalse(typeof(Vector128<double>).IsAssignableFrom(typeof(Vector128<float>)));
IsFalse(typeof(Vector128<float>).IsAssignableFrom(typeof(Vector128<int>)));
IsFalse(typeof(Vector128<int>).IsAssignableFrom(typeof(Vector128<float>)));
IsFalse(typeof(Vector4).IsAssignableFrom(typeof(Vector128<float>)));
IsFalse(typeof(Vector128<float>).IsAssignableFrom(typeof(Vector4)));
IsFalse(typeof(Vector128<float>).IsAssignableFrom(typeof(Vector<float>)));
IsFalse(typeof(Vector256<float>).IsAssignableFrom(typeof(Vector<float>)));

// System.__Canon
IsTrue (IsAssignableFrom<KeyValuePair<IDisposable, IDisposable>, KeyValuePair<IDisposable, IDisposable>>());
IsTrue (IsAssignableFrom<KeyValuePair<IDisposable, object>, KeyValuePair<IDisposable, object>>());
IsTrue (IsAssignableFrom<IDictionary<IDisposable, IDisposable>, IDictionary<IDisposable, IDisposable>>());
IsTrue (IsAssignableFrom<IDictionary<IDisposable, object>, IDictionary<IDisposable, object>>());
IsTrue (IsAssignableFrom<Dictionary<IDisposable, IDisposable>, Dictionary<IDisposable, IDisposable>>());
IsTrue (IsAssignableFrom<Dictionary<IDisposable, object>, Dictionary<IDisposable, object>>());
IsTrue (IsAssignableFrom<KeyValuePair<int, int>, KeyValuePair<int, int>>());
IsTrue (IsAssignableFrom<KeyValuePair<IEnumerable<int>, IEnumerable<int>>, KeyValuePair<IEnumerable<int>, IEnumerable<int>>>());
IsFalse(IsAssignableFrom<KeyValuePair<IDisposable, IDisposable>, KeyValuePair<IDisposable, object>>());
IsFalse(IsAssignableFrom<KeyValuePair<IDisposable, int>, KeyValuePair<IDisposable, object>>());
IsFalse(IsAssignableFrom<IDictionary<IDisposable, IDisposable>, IDictionary<IDisposable, object>>());
IsFalse(IsAssignableFrom<IDictionary<IDisposable, int>, IDictionary<IDisposable, object>>());
IsFalse(IsAssignableFrom<Dictionary<IDisposable, IDisposable>, Dictionary<IDisposable, object>>());
IsFalse(IsAssignableFrom<Dictionary<IDisposable, int>, Dictionary<IDisposable, object>>());
IsFalse(IsAssignableFrom<KeyValuePair<int, int>, KeyValuePair<int, object>>());
IsFalse(IsAssignableFrom<KeyValuePair<IEnumerable<int>, IEnumerable<int>>, KeyValuePair<IEnumerable<int>, IEnumerable<uint>>>());
}

[MethodImpl(MethodImplOptions.NoInlining)]
static bool IsAssignableFrom<TTo, TTFrom>() => typeof(TTo).IsAssignableFrom(typeof(TTFrom));
}

public struct Struct1
{
public int field1;
}

public struct Struct2
{
public int field1;
}

public struct Struct3 : IDisposable
{
public int field1;
public void Dispose(){}
}

public struct GenericStruct1<T>
{
public T field;
}

public struct GenericStruct2<T>
{
public T field;
}

public enum SimpleEnum_byte : byte
{
A, B, C
}

public enum SimpleEnum_int : int
{
A,B,C
}

public enum SimpleEnum_uint : uint
{
D,E
}

public class ClassA
{
}

public class ClassB : ClassA
{
}

public class ClassC : ClassB
{
}

public class ClassD : ClassA
{
}

public delegate void CustomAction();
16 changes: 9 additions & 7 deletions src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;

public class Program
public partial class Program
{
private static int _errors = 0;

Expand Down Expand Up @@ -98,6 +98,8 @@ public static int Main(string[] args)
ThrowsNRE(() => { IsValueTypeRef(ref _varNullableIntNull); });
ThrowsNRE(() => { IsValueTypeRef(ref _varStringNull); });

TestIsAssignableFrom();

return 100 + _errors;
}

Expand Down Expand Up @@ -135,25 +137,25 @@ public static int Main(string[] args)
private static dynamic CreateDynamic2() => new { Name = "Test" };


static void IsTrue(bool expression, [CallerLineNumber] int line = 0)
static void IsTrue(bool expression, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "")
{
if (!expression)
{
Console.WriteLine($"Line {line}: test failed (expected: true).");
Console.WriteLine($"{file}:L{line} test failed (expected: true).");
_errors++;
}
}

static void IsFalse(bool expression, [CallerLineNumber] int line = 0)
static void IsFalse(bool expression, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "")
{
if (expression)
{
Console.WriteLine($"Line {line}: test failed (expected: false).");
Console.WriteLine($"{file}:L{line} test failed (expected: false).");
_errors++;
}
}

static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0)
static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "")
{
try
{
Expand All @@ -165,7 +167,7 @@ static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0)
}
catch (Exception exc)
{
Console.WriteLine($"Line {line}: {exc}");
Console.WriteLine($"{file}:L{line} {exc}");
}
Console.WriteLine($"Line {line}: test failed (expected: NullReferenceException)");
}
Expand Down
Loading