Optimize type casts (#14420)
authorAndy Ayers <andya@microsoft.com>
Fri, 13 Oct 2017 22:55:06 +0000 (15:55 -0700)
committerGitHub <noreply@github.com>
Fri, 13 Oct 2017 22:55:06 +0000 (15:55 -0700)
JIT: optimize type casts

Implement the jit interface compareTypesForEquality method
to handle casts from known types to known types, and from
shared types to certain interface types.

Call this method in the jit for castclass and isinst, using
`gtGetClassHandle` to obtain the from type. Optimize sucessful
casts and unsuccessful isinsts when the from type is known
exactly.

Undo part of the type-equality based optimization/workaround
in the AsyncMethodBuilder code that was introduced in #14178
in favor of interface checks. There is more here that can
be done here before this issue is entirely closed and I will
look at this subsequently.

This optimization allows the jit to remove boxes that are
used solely to feed type casts, and so closes #12877.

src/jit/compiler.h
src/jit/importer.cpp
src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
src/vm/jitinterface.cpp
tests/src/jit/opt/Casts/shared.cs [new file with mode: 0644]
tests/src/jit/opt/Casts/shared.csproj [new file with mode: 0644]
tests/src/jit/opt/Casts/tests.cs [new file with mode: 0644]
tests/src/jit/opt/Casts/tests.csproj [new file with mode: 0644]

index 26196112b33d11d2995de67404a04dde3de259ea..dd4fb3068f667f109536863df2d1e1f3eb3c8550 100644 (file)
@@ -3156,6 +3156,8 @@ public:
                                           CORINFO_RESOLVED_TOKEN* pResolvedToken,
                                           bool                    isCastClass);
 
+    GenTree* impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass);
+
     bool VarTypeIsMultiByteAndCanEnreg(var_types            type,
                                        CORINFO_CLASS_HANDLE typeClass,
                                        unsigned*            typeSize,
index f655955f6ec17d6b7c8f12b96324cd73d2d0b7f4..d671894ed08b6e353880ff71f4b51521ad92d73d 100644 (file)
@@ -9762,6 +9762,99 @@ var_types Compiler::impGetByRefResultType(genTreeOps oper, bool fUnsigned, GenTr
     return type;
 }
 
+//------------------------------------------------------------------------
+// impOptimizeCastClassOrIsInst: attempt to resolve a cast when jitting
+//
+// Arguments:
+//   op1 - value to cast
+//   pResolvedToken - resolved token for type to cast to
+//   isCastClass - true if this is a castclass, false if isinst
+//
+// Return Value:
+//   tree representing optimized cast, or null if no optimization possible
+
+GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass)
+{
+    assert(op1->TypeGet() == TYP_REF);
+
+    // Don't optimize for minopts or debug codegen.
+    if (opts.compDbgCode || opts.MinOpts())
+    {
+        return nullptr;
+    }
+
+    // See what we know about the type of the object being cast.
+    bool                 isExact   = false;
+    bool                 isNonNull = false;
+    CORINFO_CLASS_HANDLE fromClass = gtGetClassHandle(op1, &isExact, &isNonNull);
+    GenTree*             optResult = nullptr;
+
+    if (fromClass != nullptr)
+    {
+        CORINFO_CLASS_HANDLE toClass = pResolvedToken->hClass;
+        JITDUMP("\nConsidering optimization of %s from %s%p (%s) to %p (%s)\n", isCastClass ? "castclass" : "isinst",
+                isExact ? "exact " : "", fromClass, info.compCompHnd->getClassName(fromClass), toClass,
+                info.compCompHnd->getClassName(toClass));
+
+        // Perhaps we know if the cast will succeed or fail.
+        TypeCompareState castResult = info.compCompHnd->compareTypesForCast(fromClass, toClass);
+
+        if (castResult == TypeCompareState::Must)
+        {
+            // Cast will succeed, result is simply op1.
+            JITDUMP("Cast will succeed, optimizing to simply return input\n");
+            return op1;
+        }
+        else if (castResult == TypeCompareState::MustNot)
+        {
+            // See if we can sharpen exactness by looking for final classes
+            if (!isExact)
+            {
+                DWORD flags     = info.compCompHnd->getClassAttribs(fromClass);
+                DWORD flagsMask = CORINFO_FLG_FINAL | CORINFO_FLG_MARSHAL_BYREF | CORINFO_FLG_CONTEXTFUL |
+                                  CORINFO_FLG_VARIANCE | CORINFO_FLG_ARRAY;
+                isExact = ((flags & flagsMask) == CORINFO_FLG_FINAL);
+            }
+
+            // Cast to exact type will fail. Handle case where we have
+            // an exact type (that is, fromClass is not a subtype)
+            // and we're not going to throw on failure.
+            if (isExact && !isCastClass)
+            {
+                JITDUMP("Cast will fail, optimizing to return null\n");
+                GenTree* result = gtNewIconNode(0, TYP_REF);
+
+                // If the cast was fed by a box, we can remove that too.
+                if (op1->IsBoxedValue())
+                {
+                    JITDUMP("Also removing upstream box\n");
+                    gtTryRemoveBoxUpstreamEffects(op1);
+                }
+
+                return result;
+            }
+            else if (isExact)
+            {
+                JITDUMP("Not optimizing failing castclass (yet)\n");
+            }
+            else
+            {
+                JITDUMP("Can't optimize since fromClass is inexact\n");
+            }
+        }
+        else
+        {
+            JITDUMP("Result of cast unknown, must generate runtime test\n");
+        }
+    }
+    else
+    {
+        JITDUMP("\nCan't optimize since fromClass is unknown\n");
+    }
+
+    return nullptr;
+}
+
 //------------------------------------------------------------------------
 // impCastClassOrIsInstToTree: build and import castclass/isinst
 //
@@ -14233,7 +14326,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
                 break;
 
             case CEE_ISINST:
-
+            {
                 /* Get the type token */
                 assertImp(sz == sizeof(unsigned));
 
@@ -14262,45 +14355,56 @@ void Compiler::impImportBlockCode(BasicBlock* block)
 
                 op1 = impPopStack().val;
 
-#ifdef FEATURE_READYTORUN_COMPILER
-                if (opts.IsReadyToRun())
+                GenTree* optTree = impOptimizeCastClassOrIsInst(op1, &resolvedToken, false);
+
+                if (optTree != nullptr)
+                {
+                    impPushOnStack(optTree, tiRetVal);
+                }
+                else
                 {
-                    GenTreeCall* opLookup =
-                        impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_ISINSTANCEOF, TYP_REF,
-                                                  gtNewArgList(op1));
-                    usingReadyToRunHelper = (opLookup != nullptr);
-                    op1                   = (usingReadyToRunHelper ? opLookup : op1);
 
-                    if (!usingReadyToRunHelper)
+#ifdef FEATURE_READYTORUN_COMPILER
+                    if (opts.IsReadyToRun())
                     {
-                        // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call
-                        // and the isinstanceof_any call with a single call to a dynamic R2R cell that will:
-                        //      1) Load the context
-                        //      2) Perform the generic dictionary lookup and caching, and generate the appropriate stub
-                        //      3) Perform the 'is instance' check on the input object
-                        // Reason: performance (today, we'll always use the slow helper for the R2R generics case)
+                        GenTreeCall* opLookup =
+                            impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_ISINSTANCEOF, TYP_REF,
+                                                      gtNewArgList(op1));
+                        usingReadyToRunHelper = (opLookup != nullptr);
+                        op1                   = (usingReadyToRunHelper ? opLookup : op1);
 
-                        op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE);
-                        if (op2 == nullptr)
-                        { // compDonotInline()
-                            return;
+                        if (!usingReadyToRunHelper)
+                        {
+                            // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call
+                            // and the isinstanceof_any call with a single call to a dynamic R2R cell that will:
+                            //      1) Load the context
+                            //      2) Perform the generic dictionary lookup and caching, and generate the appropriate
+                            //      stub
+                            //      3) Perform the 'is instance' check on the input object
+                            // Reason: performance (today, we'll always use the slow helper for the R2R generics case)
+
+                            op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE);
+                            if (op2 == nullptr)
+                            { // compDonotInline()
+                                return;
+                            }
                         }
                     }
-                }
 
-                if (!usingReadyToRunHelper)
+                    if (!usingReadyToRunHelper)
 #endif
-                {
-                    op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false);
-                }
-                if (compDonotInline())
-                {
-                    return;
-                }
-
-                impPushOnStack(op1, tiRetVal);
+                    {
+                        op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false);
+                    }
+                    if (compDonotInline())
+                    {
+                        return;
+                    }
 
+                    impPushOnStack(op1, tiRetVal);
+                }
                 break;
+            }
 
             case CEE_REFANYVAL:
 
@@ -14795,45 +14899,58 @@ void Compiler::impImportBlockCode(BasicBlock* block)
             // At this point we expect typeRef to contain the token, op1 to contain the value being cast,
             // and op2 to contain code that creates the type handle corresponding to typeRef
             CASTCLASS:
+            {
+                GenTree* optTree = impOptimizeCastClassOrIsInst(op1, &resolvedToken, true);
 
-#ifdef FEATURE_READYTORUN_COMPILER
-                if (opts.IsReadyToRun())
+                if (optTree != nullptr)
+                {
+                    impPushOnStack(optTree, tiRetVal);
+                }
+                else
                 {
-                    GenTreeCall* opLookup = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_CHKCAST,
-                                                                      TYP_REF, gtNewArgList(op1));
-                    usingReadyToRunHelper = (opLookup != nullptr);
-                    op1                   = (usingReadyToRunHelper ? opLookup : op1);
 
-                    if (!usingReadyToRunHelper)
+#ifdef FEATURE_READYTORUN_COMPILER
+                    if (opts.IsReadyToRun())
                     {
-                        // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call
-                        // and the chkcastany call with a single call to a dynamic R2R cell that will:
-                        //      1) Load the context
-                        //      2) Perform the generic dictionary lookup and caching, and generate the appropriate stub
-                        //      3) Check the object on the stack for the type-cast
-                        // Reason: performance (today, we'll always use the slow helper for the R2R generics case)
+                        GenTreeCall* opLookup =
+                            impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_CHKCAST, TYP_REF,
+                                                      gtNewArgList(op1));
+                        usingReadyToRunHelper = (opLookup != nullptr);
+                        op1                   = (usingReadyToRunHelper ? opLookup : op1);
 
-                        op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE);
-                        if (op2 == nullptr)
-                        { // compDonotInline()
-                            return;
+                        if (!usingReadyToRunHelper)
+                        {
+                            // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call
+                            // and the chkcastany call with a single call to a dynamic R2R cell that will:
+                            //      1) Load the context
+                            //      2) Perform the generic dictionary lookup and caching, and generate the appropriate
+                            //      stub
+                            //      3) Check the object on the stack for the type-cast
+                            // Reason: performance (today, we'll always use the slow helper for the R2R generics case)
+
+                            op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE);
+                            if (op2 == nullptr)
+                            { // compDonotInline()
+                                return;
+                            }
                         }
                     }
-                }
 
-                if (!usingReadyToRunHelper)
+                    if (!usingReadyToRunHelper)
 #endif
-                {
-                    op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true);
-                }
-                if (compDonotInline())
-                {
-                    return;
-                }
+                    {
+                        op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true);
+                    }
+                    if (compDonotInline())
+                    {
+                        return;
+                    }
 
-                /* Push the result back on the stack */
-                impPushOnStack(op1, tiRetVal);
-                break;
+                    /* Push the result back on the stack */
+                    impPushOnStack(op1, tiRetVal);
+                }
+            }
+            break;
 
             case CEE_THROW:
 
index e8e0d60202f163ac98f8c5f56f5b69b20ad17e69..db4a71faa5904ebffc49cf803dea033ca2deb6d3 100644 (file)
@@ -387,33 +387,14 @@ namespace System.Runtime.CompilerServices
         {
             IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine);
 
-            // TODO https://github.com/dotnet/coreclr/issues/12877:
-            // Once the JIT is able to recognize "awaiter is ITaskAwaiter" and "awaiter is IConfiguredTaskAwaiter",
-            // use those in order to a) consolidate a lot of this code, and b) handle all Task/Task<T> and not just
-            // the few types special-cased here.  For now, handle common {Configured}TaskAwaiter.  Having the types
-            // explicitly listed here allows the JIT to generate the best code for them; otherwise we'll fall through
-            // to the later workaround.
-            if (typeof(TAwaiter) == typeof(TaskAwaiter) ||
-                typeof(TAwaiter) == typeof(TaskAwaiter<object>) ||
-                typeof(TAwaiter) == typeof(TaskAwaiter<string>) ||
-                typeof(TAwaiter) == typeof(TaskAwaiter<byte[]>) ||
-                typeof(TAwaiter) == typeof(TaskAwaiter<bool>) ||
-                typeof(TAwaiter) == typeof(TaskAwaiter<byte>) ||
-                typeof(TAwaiter) == typeof(TaskAwaiter<int>) ||
-                typeof(TAwaiter) == typeof(TaskAwaiter<long>))
+            // TThe null tests here ensure that the jit can optimize away the interface
+            // tests when TAwaiter is is a ref type.
+            if ((null != (object)default(TAwaiter)) && (awaiter is ITaskAwaiter))
             {
                 ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout
                 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
             }
-            else if (
-                typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
-                typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable<object>.ConfiguredTaskAwaiter) ||
-                typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable<string>.ConfiguredTaskAwaiter) ||
-                typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable<byte[]>.ConfiguredTaskAwaiter) ||
-                typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable<bool>.ConfiguredTaskAwaiter) ||
-                typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable<byte>.ConfiguredTaskAwaiter) ||
-                typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable<int>.ConfiguredTaskAwaiter) ||
-                typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable<long>.ConfiguredTaskAwaiter))
+            else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter))
             {
                 ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
                 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
@@ -450,21 +431,6 @@ namespace System.Runtime.CompilerServices
                 TaskAwaiter.UnsafeOnCompletedInternal(vta.AsTask(), box, vta._continueOnCapturedContext);
             }
 
-            // To catch all Task/Task<T> awaits, do the currently more expensive interface checks.
-            // Eventually these and the above Task/Task<T> checks should be replaced by "is" checks,
-            // once that's recognized and optimized by the JIT.  We do these after all of the hardcoded
-            // checks above so that they don't incur the costs of these checks.
-            else if (InterfaceIsCheckWorkaround<TAwaiter>.IsITaskAwaiter)
-            {
-                ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter);
-                TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
-            }
-            else if (InterfaceIsCheckWorkaround<TAwaiter>.IsIConfiguredTaskAwaiter)
-            {
-                ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
-                TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
-            }
-
             // The awaiter isn't specially known. Fall back to doing a normal await.
             else
             {
@@ -922,13 +888,6 @@ namespace System.Runtime.CompilerServices
             new Task<TResult>(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
     }
 
-    /// <summary>Temporary workaround for https://github.com/dotnet/coreclr/issues/12877.</summary>
-    internal static class InterfaceIsCheckWorkaround<TAwaiter>
-    {
-        internal static readonly bool IsITaskAwaiter = typeof(TAwaiter).GetInterface("ITaskAwaiter") != null;
-        internal static readonly bool IsIConfiguredTaskAwaiter = typeof(TAwaiter).GetInterface("IConfiguredTaskAwaiter") != null;
-    }
-
     /// <summary>
     /// An interface implemented by all <see cref="AsyncStateMachineBox{TStateMachine, TResult}"/> instances, regardless of generics.
     /// </summary>
index ff3f3c29c52f7190f2184bbf671d44b38e57a0ab..aef25975471c6ab32feab6595011edc9fd020095 100644 (file)
@@ -4591,8 +4591,89 @@ TypeCompareState CEEInfo::compareTypesForCast(
         CORINFO_CLASS_HANDLE        fromClass,
         CORINFO_CLASS_HANDLE        toClass)
 {
-    // Stub for now
-    return TypeCompareState::May;
+    CONTRACTL {
+        SO_TOLERANT;
+        THROWS;
+        GC_TRIGGERS;
+        MODE_PREEMPTIVE;
+    } CONTRACTL_END;
+
+    TypeCompareState result = TypeCompareState::May;
+
+    JIT_TO_EE_TRANSITION();
+
+    TypeHandle fromHnd = (TypeHandle) fromClass;
+    TypeHandle toHnd = (TypeHandle) toClass;
+
+#ifdef FEATURE_COMINTEROP
+    // If casting from a com object class, don't try to optimize.
+    if (fromHnd.IsComObjectType())
+    {
+        result = TypeCompareState::May;
+    }
+    else
+#endif // FEATURE_COMINTEROP
+
+    // If casting from ICastable, don't try to optimize
+    if (fromHnd.GetMethodTable()->IsICastable())
+    {
+        result = TypeCompareState::May;
+    }
+    // If casting to Nullable<T>, don't try to optimize
+    else if (Nullable::IsNullableType(toHnd))
+    {
+        result = TypeCompareState::May;
+    }
+    // If the types are not shared, we can check directly.
+    else if (!fromHnd.IsCanonicalSubtype() && !toHnd.IsCanonicalSubtype())
+    {
+        result = fromHnd.CanCastTo(toHnd) ? TypeCompareState::Must : TypeCompareState::MustNot;
+    }
+    // Casting from a shared type to an unshared type.
+    else if (fromHnd.IsCanonicalSubtype() && !toHnd.IsCanonicalSubtype())
+    {
+        // Only handle casts to interface types for now
+        if (toHnd.IsInterface())
+        {
+            // Do a preliminary check.
+            BOOL canCast = fromHnd.CanCastTo(toHnd);
+
+            // Pass back positive results unfiltered. The unknown type
+            // parameters in fromClass did not come into play.
+            if (canCast)
+            {
+                result = TypeCompareState::Must;
+            }
+            // For negative results, the unknown type parameter in
+            // fromClass might match some instantiated interface,
+            // either directly or via variance.
+            //
+            // However, CanCastTo will report failure in such cases since
+            // __Canon won't match the instantiated type on the
+            // interface (which can't be __Canon since we screened out
+            // canonical subtypes for toClass above). So only report
+            // failure if the interface is not instantiated.
+            else if (!toHnd.HasInstantiation())
+            {
+                result = TypeCompareState::MustNot;
+            }
+        }
+    }
+
+#ifdef FEATURE_READYTORUN_COMPILER
+    // In R2R it is a breaking change for a previously positive
+    // cast to become negative, but not for a previously negative
+    // cast to become positive. So in R2R a negative result is
+    // always reported back as May.
+    if (IsReadyToRunCompilation() && (result == TypeCompareState::MustNot))
+    {
+        result = TypeCompareState::May;
+    }
+#endif // FEATURE_READYTORUN_COMPILER
+
+    EE_TO_JIT_TRANSITION();
+
+    return result;
 }
 
 /*********************************************************************/
diff --git a/tests/src/jit/opt/Casts/shared.cs b/tests/src/jit/opt/Casts/shared.cs
new file mode 100644 (file)
index 0000000..94ecd6d
--- /dev/null
@@ -0,0 +1,51 @@
+// 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.Runtime.CompilerServices;
+
+interface I<T>
+{
+    int E(T t);
+}
+
+sealed class J<T> : I<T>
+{
+    public int E(T t) { return 3; }
+}
+
+class Z
+{
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool F0<T>(I<T> i)
+    {
+        return i is I<object>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool F1<T>(J<T> j)
+    {
+        return j is I<string>;
+    }
+
+    public static int Main()
+    {
+        var j0 = new J<object>();
+        var j1 = new J<string>();
+        bool b00 = F0(j0);
+        bool b01 = F0(j1);
+        bool b10 = F1(j0);
+        bool b11 = F1(j1);
+
+        int a = 0;
+        if (b00) a += 1;
+        if (b01) a += 2;
+        if (b10) a += 4;
+        if (b11) a += 8;
+
+        Console.WriteLine($"a = {a}");
+
+        return a == 9 ? 100 : 0;
+    }
+}
diff --git a/tests/src/jit/opt/Casts/shared.csproj b/tests/src/jit/opt/Casts/shared.csproj
new file mode 100644 (file)
index 0000000..6dc39ca
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>$(MSBuildProjectName)</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT  .0\UITestExtensionPackages</ReferencePath>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <NuGetPackageImportStamp>7a9bfb7d</NuGetPackageImportStamp>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <PropertyGroup>
+    <DebugType>PdbOnly</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="shared.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/tests/src/jit/opt/Casts/tests.cs b/tests/src/jit/opt/Casts/tests.cs
new file mode 100644 (file)
index 0000000..9a2dc40
--- /dev/null
@@ -0,0 +1,213 @@
+// 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.Runtime.CompilerServices;
+
+interface I<T>
+{
+    int E(T t);
+}
+
+sealed class J : I<string>
+{
+    public int E(string s)
+    {
+        return s.Length;
+    }
+}
+
+class K : I<string>
+{
+    public int E(string s)
+    {
+        return s.GetHashCode();
+    }
+}
+
+sealed class L : K, I<object>
+{
+    public int E(object o)
+    {
+        return o.GetHashCode();
+    }
+}
+
+class F
+{
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsIString<T>(I<T> i)
+    {
+        return i is I<string>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsI<T,U>(I<U> i)
+    {
+        return i is I<T>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsJI<T>(J j)
+    {
+        return j is I<T>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsKI<T>(K k)
+    {
+        return k is I<T>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsLI<T>(L l)
+    {
+        return l is I<T>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsJIString(J j)
+    {
+        return j is I<string>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsKIString(K k)
+    {
+        return k is I<string>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsLIString(L l)
+    {
+        return l is I<string>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsJIObject(J j)
+    {
+        return j is I<object>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsKIObject(K k)
+    {
+        return k is I<object>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsLIObject(L l)
+    {
+        return l is I<object>;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsIStringJ(I<string> i)
+    {
+        return i is J;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsIStringK(I<string> i)
+    {
+        return i is K;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsIStringL(I<string> i)
+    {
+        return i is L;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsIJ<T>(I<T> i)
+    {
+        return i is J;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsIK<T>(I<T> i)
+    {
+        return i is K;
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsIL<T>(I<T> i)
+    {
+        return i is K;
+    }
+
+    public static int Main()
+    {
+        var j = new J();
+        var k = new K();
+        var l = new L();
+        
+        bool b0 = IsIString(j);
+        bool b1 = IsIString(k);
+        bool b2 = IsIString<string>(l);
+        bool b3 = IsIString<object>(l);
+
+        bool c0 = IsI<string,string>(j);
+        bool c1 = IsI<string,string>(k);
+        bool c2 = IsI<string,string>(l);
+
+        bool d0 = IsI<object,string>(j);
+        bool d1 = IsI<object,string>(k);
+        bool d2 = IsI<object,string>(l);
+
+        bool e0 = IsJI<string>(j);
+        bool e1 = IsKI<string>(k);
+        bool e2 = IsKI<string>(l);
+        bool e3 = IsLI<string>(l);
+
+        bool f0 = IsJIString(j);
+        bool f1 = IsKIString(k);
+        bool f2 = IsKIString(l);
+        bool f3 = IsLIString(l);
+
+        bool g0 = IsIStringJ(j);
+        bool g1 = IsIStringJ(k);
+        bool g2 = IsIStringJ(l);
+        bool g3 = IsIStringK(j);
+        bool g4 = IsIStringK(k);
+        bool g5 = IsIStringK(l);
+        bool g6 = IsIStringL(j);
+        bool g7 = IsIStringL(k);
+        bool g8 = IsIStringL(l);
+
+        bool h0 = IsIJ<string>(j);
+        bool h1 = IsIJ<string>(k);
+        bool h2 = IsIJ<string>(l);
+        bool h3 = IsIK<string>(j);
+        bool h4 = IsIK<string>(k);
+        bool h5 = IsIK<string>(l);
+        bool h6 = IsIL<string>(j);
+        bool h7 = IsIL<string>(k);
+        bool h8 = IsIL<string>(l);
+
+        bool j0 = IsJIObject(j);
+        bool j1 = IsKIObject(k);
+        bool j2 = IsKIObject(l);
+        bool j3 = IsLIObject(l);
+
+        bool pos = 
+        b0 & b1 & b2 & b3 
+        & c0 & c1 & c2
+        & d2
+        & e0 & e1 & e2 & e3 
+        & f0 & f1 & f2 & f3
+        & g0 & g4 & g5 & g8
+        & h0 & h4 & h5 & h8
+        & j2 & j3;
+
+        bool neg = 
+        d0 & d1
+        & g1 & g2 & g6 & g7
+        & h1 & h2 & h6 & h7
+        & j0 & j1;
+
+        return pos & !neg ? 100 : 0;
+    }
+}
diff --git a/tests/src/jit/opt/Casts/tests.csproj b/tests/src/jit/opt/Casts/tests.csproj
new file mode 100644 (file)
index 0000000..c6d0731
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>$(MSBuildProjectName)</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT  .0\UITestExtensionPackages</ReferencePath>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <NuGetPackageImportStamp>7a9bfb7d</NuGetPackageImportStamp>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <PropertyGroup>
+    <DebugType>PdbOnly</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="tests.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
\ No newline at end of file