diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 26196112b33d..dd4fb3068f66 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -3156,6 +3156,8 @@ class Compiler 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, diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index f655955f6ec1..d671894ed08b 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -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: diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs index e8e0d60202f1..db4a71faa590 100644 --- a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -387,33 +387,14 @@ public void AwaitUnsafeOnCompleted( { 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 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) || - typeof(TAwaiter) == typeof(TaskAwaiter) || - typeof(TAwaiter) == typeof(TaskAwaiter) || - typeof(TAwaiter) == typeof(TaskAwaiter) || - typeof(TAwaiter) == typeof(TaskAwaiter) || - typeof(TAwaiter) == typeof(TaskAwaiter) || - typeof(TAwaiter) == typeof(TaskAwaiter)) + // 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(ref awaiter); // relies on TaskAwaiter/TaskAwaiter having the same layout TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true); } - else if ( - typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter)) + else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter)) { ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As(ref awaiter); TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext); @@ -450,21 +431,6 @@ public void AwaitUnsafeOnCompleted( TaskAwaiter.UnsafeOnCompletedInternal(vta.AsTask(), box, vta._continueOnCapturedContext); } - // To catch all Task/Task awaits, do the currently more expensive interface checks. - // Eventually these and the above Task/Task 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.IsITaskAwaiter) - { - ref TaskAwaiter ta = ref Unsafe.As(ref awaiter); - TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true); - } - else if (InterfaceIsCheckWorkaround.IsIConfiguredTaskAwaiter) - { - ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As(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 @@ internal static Task CreateCacheableTask(TResult result) => new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken)); } - /// Temporary workaround for https://github.com/dotnet/coreclr/issues/12877. - internal static class InterfaceIsCheckWorkaround - { - internal static readonly bool IsITaskAwaiter = typeof(TAwaiter).GetInterface("ITaskAwaiter") != null; - internal static readonly bool IsIConfiguredTaskAwaiter = typeof(TAwaiter).GetInterface("IConfiguredTaskAwaiter") != null; - } - /// /// An interface implemented by all instances, regardless of generics. /// diff --git a/src/vm/jitinterface.cpp b/src/vm/jitinterface.cpp index ff3f3c29c52f..aef25975471c 100644 --- a/src/vm/jitinterface.cpp +++ b/src/vm/jitinterface.cpp @@ -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, 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 index 000000000000..94ecd6de4427 --- /dev/null +++ b/tests/src/jit/opt/Casts/shared.cs @@ -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 +{ + int E(T t); +} + +sealed class J : I +{ + public int E(T t) { return 3; } +} + +class Z +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static bool F0(I i) + { + return i is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool F1(J j) + { + return j is I; + } + + public static int Main() + { + var j0 = new J(); + var j1 = new J(); + 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 index 000000000000..6dc39ca21a59 --- /dev/null +++ b/tests/src/jit/opt/Casts/shared.csproj @@ -0,0 +1,38 @@ + + + + + Debug + AnyCPU + $(MSBuildProjectName) + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + Properties + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(ProgramFiles)\Common Files\microsoft shared\VSTT .0\UITestExtensionPackages + ..\..\ + 7a9bfb7d + + + + + + + False + + + + PdbOnly + True + + + + + + + + + + \ 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 index 000000000000..9a2dc400ad1d --- /dev/null +++ b/tests/src/jit/opt/Casts/tests.cs @@ -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 +{ + int E(T t); +} + +sealed class J : I +{ + public int E(string s) + { + return s.Length; + } +} + +class K : I +{ + public int E(string s) + { + return s.GetHashCode(); + } +} + +sealed class L : K, I +{ + public int E(object o) + { + return o.GetHashCode(); + } +} + +class F +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsIString(I i) + { + return i is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsI(I i) + { + return i is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsJI(J j) + { + return j is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsKI(K k) + { + return k is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsLI(L l) + { + return l is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsJIString(J j) + { + return j is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsKIString(K k) + { + return k is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsLIString(L l) + { + return l is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsJIObject(J j) + { + return j is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsKIObject(K k) + { + return k is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsLIObject(L l) + { + return l is I; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsIStringJ(I i) + { + return i is J; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsIStringK(I i) + { + return i is K; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsIStringL(I i) + { + return i is L; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsIJ(I i) + { + return i is J; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsIK(I i) + { + return i is K; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsIL(I 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(l); + bool b3 = IsIString(l); + + bool c0 = IsI(j); + bool c1 = IsI(k); + bool c2 = IsI(l); + + bool d0 = IsI(j); + bool d1 = IsI(k); + bool d2 = IsI(l); + + bool e0 = IsJI(j); + bool e1 = IsKI(k); + bool e2 = IsKI(l); + bool e3 = IsLI(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(j); + bool h1 = IsIJ(k); + bool h2 = IsIJ(l); + bool h3 = IsIK(j); + bool h4 = IsIK(k); + bool h5 = IsIK(l); + bool h6 = IsIL(j); + bool h7 = IsIL(k); + bool h8 = IsIL(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 index 000000000000..c6d0731b1b63 --- /dev/null +++ b/tests/src/jit/opt/Casts/tests.csproj @@ -0,0 +1,38 @@ + + + + + Debug + AnyCPU + $(MSBuildProjectName) + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + Properties + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(ProgramFiles)\Common Files\microsoft shared\VSTT .0\UITestExtensionPackages + ..\..\ + 7a9bfb7d + + + + + + + False + + + + PdbOnly + True + + + + + + + + + + \ No newline at end of file