Intrinsify typeof(T).IsValueType (#1157)
authorEgor Bogatov <egorbo@gmail.com>
Fri, 27 Dec 2019 19:42:39 +0000 (22:42 +0300)
committerJan Kotas <jkotas@microsoft.com>
Fri, 27 Dec 2019 19:42:39 +0000 (11:42 -0800)
src/coreclr/src/jit/importer.cpp
src/coreclr/src/jit/namedintrinsiclist.h
src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics.cs [new file with mode: 0644]
src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_il.il [new file with mode: 0644]
src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_il.ilproj [new file with mode: 0644]
src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_r.csproj [new file with mode: 0644]
src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_ro.csproj [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Type.cs

index a409192..866df11 100644 (file)
@@ -4015,6 +4015,36 @@ GenTree* Compiler::impIntrinsic(GenTree*                newobjThis,
                 break;
             }
 
+            case NI_System_Type_get_IsValueType:
+            {
+                // Optimize
+                //
+                //   call Type.GetTypeFromHandle (which is replaced with CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE)
+                //   call Type.IsValueType
+                //
+                // to `true` or `false`
+                // e.g. `typeof(int).IsValueType` => `true`
+                if (impStackTop().val->IsCall())
+                {
+                    GenTreeCall* call = impStackTop().val->AsCall();
+                    if (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE))
+                    {
+                        CORINFO_CLASS_HANDLE hClass = gtGetHelperArgClassHandle(call->gtCallArgs->GetNode());
+                        if (hClass != NO_CLASS_HANDLE)
+                        {
+                            retNode =
+                                gtNewIconNode((eeIsValueClass(hClass) &&
+                                               // pointers are not value types (e.g. typeof(int*).IsValueType is false)
+                                               info.compCompHnd->asCorInfoType(hClass) != CORINFO_TYPE_PTR)
+                                                  ? 1
+                                                  : 0);
+                            impPopStack(); // drop CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE call
+                        }
+                    }
+                }
+                break;
+            }
+
 #ifdef FEATURE_HW_INTRINSICS
             case NI_System_Math_FusedMultiplyAdd:
             case NI_System_MathF_FusedMultiplyAdd:
@@ -4306,6 +4336,13 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
                 result = NI_System_GC_KeepAlive;
             }
         }
+        else if (strcmp(className, "Type") == 0)
+        {
+            if (strcmp(methodName, "get_IsValueType") == 0)
+            {
+                result = NI_System_Type_get_IsValueType;
+            }
+        }
     }
 #if defined(_TARGET_XARCH_) // We currently only support BSWAP on x86
     else if (strcmp(namespaceName, "System.Buffers.Binary") == 0)
index a9e4565..77af045 100644 (file)
@@ -19,6 +19,7 @@ enum NamedIntrinsic : unsigned short
     NI_System_Collections_Generic_EqualityComparer_get_Default,
     NI_System_Buffers_Binary_BinaryPrimitives_ReverseEndianness,
     NI_System_GC_KeepAlive,
+    NI_System_Type_get_IsValueType,
 
 #ifdef FEATURE_HW_INTRINSICS
     NI_IsSupported_True,
diff --git a/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics.cs b/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics.cs
new file mode 100644 (file)
index 0000000..6dee554
--- /dev/null
@@ -0,0 +1,177 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+
+public class Program
+{
+    private static int _errors = 0;
+
+    public static int Main(string[] args)
+    {
+        IsTrue (typeof(byte).IsValueType);
+        IsTrue (typeof(int).IsValueType);
+        IsTrue (typeof(int?).IsValueType);
+        IsFalse(typeof(int*).IsValueType);
+        IsFalse(typeof(int**).IsValueType);
+        IsFalse(typeof(void*).IsValueType);
+        IsFalse(typeof(void**).IsValueType);
+        IsFalse(typeof(GenericStruct<int>*).IsValueType);
+        IsTrue (typeof(IntPtr).IsValueType);
+        IsTrue (typeof(decimal).IsValueType);
+        IsTrue (typeof(double).IsValueType);
+        IsFalse(typeof(string).IsValueType);
+        IsFalse(typeof(object).IsValueType);
+        IsFalse(typeof(object[]).IsValueType);
+        IsFalse(typeof(int[]).IsValueType);
+        IsFalse(typeof(int[,,]).IsValueType);
+        IsFalse(typeof(IEnumerable<int>).IsValueType);
+        IsFalse(typeof(Action<int>).IsValueType);
+        IsTrue (typeof(GenericStruct<int>).IsValueType);
+        IsTrue (typeof(GenericStruct<string>).IsValueType);
+        IsTrue (typeof(GenericStruct<string>).IsValueType);
+        IsTrue (typeof(KeyValuePair<int, string>).IsValueType);
+        IsTrue (typeof(KeyValuePair<Program, string>).IsValueType);
+        IsTrue (typeof(SimpleEnum).IsValueType);
+        IsTrue (typeof(void).IsValueType);
+        IsFalse(typeof(ValueType).IsValueType);
+        IsFalse(typeof(List<>).IsValueType);
+        IsFalse(typeof(IDictionary<,>).IsValueType);
+        IsTrue (typeof(Vector128<>).IsValueType);
+        IsTrue (typeof(Vector128<byte>).IsValueType);
+
+        // Test __Canon
+        IsFalse(IsValueType<IEnumerable<int>>());
+        IsFalse(IsValueType<IEnumerable<string>>());
+        IsFalse(IsValueType<IEnumerable<IDisposable>>());
+        IsFalse(IsValueType<IDictionary<int, string>>());
+        IsFalse(IsValueType<IDictionary<IConvertible, IComparer<int>>>());
+        IsFalse(IsValueType<Dictionary<int, int>>());
+        IsFalse(IsValueType<Dictionary<string, IEnumerable>>());
+
+        // Test `x.GetType().IsX`
+        IsTrue (IsValueType<int>(42));
+        IsTrue (IsValueType<int?>(new Nullable<int>(42)));
+        IsTrue (IsValueType<decimal>(42M));
+        IsFalse(IsValueType<string>("42"));
+        IsFalse(IsValueType<object>(new object()));
+        IsFalse(IsValueType<IEnumerable<int>>(new int[10]));
+        IsFalse(IsValueType<Action<int>>(_ => { }));
+        IsTrue (IsValueType<GenericStruct<int>>(default));
+        IsTrue (IsValueType<GenericStruct<string>>(default));
+        IsTrue (IsValueType(SimpleEnum.B));
+        IsTrue (IsValueType(CreateDynamic1()));
+        IsFalse(IsValueType(CreateDynamic2()));
+
+        IsTrue (IsValueTypeObj(42));
+        IsTrue (IsValueTypeObj(new Nullable<int>(42)));
+        IsTrue (IsValueTypeObj(42M));
+        IsFalse(IsValueTypeObj("42"));
+        IsFalse(IsValueTypeObj(new object()));
+        IsFalse(IsValueTypeObj(new int[10]));
+        IsFalse(IsValueTypeObj((Action<int>)(_ => { })));
+        IsTrue (IsValueTypeObj(new GenericStruct<int>()));
+        IsTrue (IsValueTypeObj(new GenericStruct<string>()));
+        IsTrue (IsValueTypeObj(SimpleEnum.B));
+        IsTrue (IsValueTypeObj(CreateDynamic1()));
+        IsFalse(IsValueTypeObj(CreateDynamic2()));
+
+        IsTrue (IsValueTypeRef(ref _varInt));
+        IsTrue (IsValueTypeRef(ref _varNullableInt));
+        IsTrue (IsValueTypeRef(ref _varDecimal));
+        IsFalse(IsValueTypeRef(ref _varString));
+        IsFalse(IsValueTypeRef(ref _varObject));
+        IsFalse(IsValueTypeRef(ref _varArrayOfInt));
+        IsFalse(IsValueTypeRef(ref _varAction));
+        IsTrue (IsValueTypeRef(ref _varGenericStructInt));
+        IsTrue (IsValueTypeRef(ref _varGenericStructStr));
+        IsTrue (IsValueTypeRef(ref _varEnum));
+
+        ThrowsNRE(() => { IsValueType(_varNullableIntNull); });
+        ThrowsNRE(() => { IsValueType(_varStringNull); });
+        ThrowsNRE(() => { IsValueTypeRef(ref _varNullableIntNull); });
+        ThrowsNRE(() => { IsValueTypeRef(ref _varStringNull); });
+
+        return 100 + _errors;
+    }
+
+    private static int _varInt = 42;
+    private static int? _varNullableInt = 42;
+    private static decimal _varDecimal = 42M;
+    private static string _varString = "42";
+    private static object _varObject = new object();
+    private static int[] _varArrayOfInt = new int[10];
+    private static Action<int> _varAction = _ => { };
+    private static GenericStruct<int> _varGenericStructInt = new GenericStruct<int> { field = 42 };
+    private static GenericStruct<string> _varGenericStructStr = new GenericStruct<string> { field = "42" };
+    private static SimpleEnum _varEnum = SimpleEnum.B;
+
+    private static int? _varNullableIntNull = null;
+    private static string _varStringNull = null;
+
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static bool IsValueType<T>() => typeof(T).IsValueType;
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsValueType<T>(T val) => val.GetType().IsValueType;
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static bool IsValueTypeRef<T>(ref T val) => val.GetType().IsValueType;
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static bool IsValueTypeObj(object val) => val.GetType().IsValueType;
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static dynamic CreateDynamic1() => 42;
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static dynamic CreateDynamic2() => new { Name = "Test" };
+
+
+    static void IsTrue(bool expression, [CallerLineNumber] int line = 0)
+    {
+        if (!expression)
+        {
+            Console.WriteLine($"Line {line}: test failed (expected: true).");
+            _errors++;
+        }
+    }
+
+    static void IsFalse(bool expression, [CallerLineNumber] int line = 0)
+    {
+        if (expression)
+        {
+            Console.WriteLine($"Line {line}: test failed (expected: false).");
+            _errors++;
+        }
+    }
+
+    static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0)
+    {
+        try
+        {
+            action();
+        }
+        catch (NullReferenceException)
+        {
+            return;
+        }
+        catch (Exception exc)
+        {
+            Console.WriteLine($"Line {line}: {exc}");
+        }
+        Console.WriteLine($"Line {line}: test failed (expected: NullReferenceException)");
+    }
+}
+
+public struct GenericStruct<T>
+{
+    public T field;
+}
+
+public enum SimpleEnum
+{
+    A,B,C
+}
\ No newline at end of file
diff --git a/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_il.il b/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_il.il
new file mode 100644 (file)
index 0000000..aa9fee6
--- /dev/null
@@ -0,0 +1,47 @@
+// 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.
+
+.assembly extern mscorlib { }
+
+.assembly TypeIntrinsicsTests
+{
+  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
+}
+
+.class private auto ansi beforefieldinit Test
+       extends [mscorlib]System.Object
+{
+  .method private hidebysig static int32 Main() cil managed
+  {
+    .entrypoint
+    .maxstack  1
+    // it's not currently possible to produce `ldtoken string&` in C#
+    ldtoken    [System.Runtime]System.String&
+    call       class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
+    call       instance bool [System.Runtime]System.Type::get_IsValueType()
+    brtrue     FAILED
+
+    ldtoken    [System.Runtime]System.Int16&
+    call       class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
+    call       instance bool [System.Runtime]System.Type::get_IsValueType()
+    brfalse.s  FAILED
+
+    ldtoken    [System.Runtime]System.Object&
+    call       class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
+    call       instance bool [System.Runtime]System.Type::get_IsValueType()
+    brtrue.s   FAILED
+
+    ldtoken    [System.Runtime]System.Decimal&
+    call       class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
+    call       instance bool [System.Runtime]System.Type::get_IsValueType()
+    brfalse.s  FAILED
+
+    ldc.i4.s   100
+    ret
+
+FAILED:
+    ldc.i4.s   42
+    ret
+  }
+}
\ No newline at end of file
diff --git a/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_il.ilproj b/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_il.ilproj
new file mode 100644 (file)
index 0000000..4a2b605
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk.IL">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>PdbOnly</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="TypeIntrinsics_il.il" />
+  </ItemGroup>
+</Project>
diff --git a/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_r.csproj b/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_r.csproj
new file mode 100644 (file)
index 0000000..22afa8a
--- /dev/null
@@ -0,0 +1,10 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <DebugType>None</DebugType>
+    <Optimize />
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="TypeIntrinsics.cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_ro.csproj b/src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_ro.csproj
new file mode 100644 (file)
index 0000000..b8ce948
--- /dev/null
@@ -0,0 +1,10 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="TypeIntrinsics.cs" />
+  </ItemGroup>
+</Project>
index c525497..28a6c98 100644 (file)
@@ -6,6 +6,7 @@ using System.Threading;
 using System.Reflection;
 using System.Diagnostics;
 using System.Globalization;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
 namespace System
@@ -107,7 +108,7 @@ namespace System
         protected virtual bool IsMarshalByRefImpl() => false;
         public bool IsPrimitive => IsPrimitiveImpl();
         protected abstract bool IsPrimitiveImpl();
-        public bool IsValueType => IsValueTypeImpl();
+        public bool IsValueType { [Intrinsic] get => IsValueTypeImpl(); }
         protected virtual bool IsValueTypeImpl() => IsSubclassOf(typeof(ValueType));
 
         public virtual bool IsSignatureType => false;