Skip to content

Commit

Permalink
Revert "Revert "Optimize typeof(T1).IsAssignableFrom(typeof(T2))" (do…
Browse files Browse the repository at this point in the history
…tnet#31643)" (dotnet#31705)

This reverts commit 729276d.
  • Loading branch information
jkotas authored Feb 4, 2020
1 parent b1d93c8 commit 2ddc9d2
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 12 deletions.
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()`
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

0 comments on commit 2ddc9d2

Please sign in to comment.