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

Optimize typeof(T).IsValueType #1157

Merged
merged 27 commits into from
Dec 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5ef9ec3
Intrinsify typeof(T).IsValueType, IsClass, IsPrimitiveType
EgorBo Dec 26, 2019
ff69f86
Add tests
EgorBo Dec 26, 2019
ffbacad
Improve tests, fix copy-paste
EgorBo Dec 26, 2019
5868336
Improve tests, add Enum
EgorBo Dec 26, 2019
7bf4a12
Ignore GT_RUNTIMELOOKUP argument
EgorBo Dec 26, 2019
76a0312
Add IL tests (for ldtoken type& case)
EgorBo Dec 26, 2019
86e7842
fix formatting issues
EgorBo Dec 26, 2019
b71f1be
Handle Enum (is not primitive), improve tests, add more test cases
EgorBo Dec 26, 2019
5c8ff26
Add more test cases
EgorBo Dec 26, 2019
631a9ca
implement isEnum
EgorBo Dec 26, 2019
ac03c32
re-implement isEnum check
EgorBo Dec 26, 2019
3b37a90
formatting issues
EgorBo Dec 26, 2019
0926bbf
temp commit: disable optimization to check my tests
EgorBo Dec 26, 2019
b24213f
Simplify IL tests
EgorBo Dec 26, 2019
e36adb3
Fix copy-paste errors in tests, add more test cases.
EgorBo Dec 26, 2019
627e7a0
Use gtGetHelperArgClassHandle
EgorBo Dec 26, 2019
1fc0b71
Add tests for __Canon
EgorBo Dec 26, 2019
6b37368
Use canInlineTypeCheckWithObjectVTable() to detect __Canon
EgorBo Dec 27, 2019
3a53e71
fix formatting issues
EgorBo Dec 27, 2019
363d683
Add __Canon to well-known types
EgorBo Dec 27, 2019
fc0d723
Cleanup
EgorBo Dec 27, 2019
22a064a
Cleanup
EgorBo Dec 27, 2019
75d5c84
Cleanup
EgorBo Dec 27, 2019
a6f6f13
Remove IsClass and IsPrimitive
EgorBo Dec 27, 2019
b7e054f
Formatting
EgorBo Dec 27, 2019
5837874
Use asCorInfoType instead of getTypeForPrimitiveValueClass
EgorBo Dec 27, 2019
5bfe840
formatting
EgorBo Dec 27, 2019
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
37 changes: 37 additions & 0 deletions src/coreclr/src/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
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 @@ -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,
Expand Down
177 changes: 177 additions & 0 deletions src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics.cs
Original file line number Diff line number Diff line change
@@ -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
}
47 changes: 47 additions & 0 deletions src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_il.il
Original file line number Diff line number Diff line change
@@ -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
}
}
13 changes: 13 additions & 0 deletions src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_il.ilproj
Original file line number Diff line number Diff line change
@@ -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>
10 changes: 10 additions & 0 deletions src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_r.csproj
Original file line number Diff line number Diff line change
@@ -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>
10 changes: 10 additions & 0 deletions src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics_ro.csproj
Original file line number Diff line number Diff line change
@@ -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>
3 changes: 2 additions & 1 deletion src/libraries/System.Private.CoreLib/src/System/Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System
Expand Down Expand Up @@ -107,7 +108,7 @@ public virtual Type[] GetGenericParameterConstraints()
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;
Expand Down