From ccc5e17738a4b2f652059f31e8146513163dbee7 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 27 Sep 2017 07:15:04 -0700 Subject: [PATCH] JIT: devirtualization support for EqualityComparer.Default (#14125) Mark `EqualityComparer.Default`'s getter as `[Intrinsic]` so the jit knows there is something special about it. Extend the jit's named intrinsic recognizer to recognize this method. Add a new jit interface method to determine the exact type returned by `EqualityComparer.Default`, given `T`. Compute the return type by mirroring the logic used in the actual implementation. Bail out when `T` is not final as those cases won't simplify down much and lead to code bloat. Invoke this interface method when trying to devirtualize calls where the 'this' object in the call comes from `EqualityComparer.Default`. The devirtualized methods can then be inlined. Since the specific comparer `Equal` and `GetHashCode` methods look more complicated in IL than they really are, mark them with AggressiveInlining attributes. If devirtualization and inlining happen, it is quite likely that the value of the comparer object itself is not used in the body of the comparer. This value comes from a static field cache on the comparer helper. When the comparer value is ignored, the jit removes the field access since it is non-faulting. It also removes the the class init helper that is there to ensure that the (no-longer accessed) field is properly initialized. This helper has relatively high overhead even in the fast case where the class has been initialized aready. Add a perf test. Closes #6688. --- .../superpmi-shared/icorjitinfoimpl.h | 12 +- .../superpmi/superpmi-shared/lwmlist.h | 1 + .../superpmi-shared/methodcontext.cpp | 17 ++ .../superpmi/superpmi-shared/methodcontext.h | 8 +- .../superpmi-shim-collector/icorjitinfo.cpp | 10 + .../superpmi-shim-counter/icorjitinfo.cpp | 8 + .../superpmi-shim-simple/icorjitinfo.cpp | 7 + src/ToolBox/superpmi/superpmi/icorjitinfo.cpp | 9 + src/inc/corinfo.h | 16 +- src/jit/ICorJitInfo_API_names.h | 1 + src/jit/ICorJitInfo_API_wrapper.hpp | 10 + src/jit/compiler.h | 3 +- src/jit/flowgraph.cpp | 107 ++++++++- src/jit/gentree.cpp | 1 + src/jit/gentree.h | 4 +- src/jit/importer.cpp | 173 +++++++++++---- src/jit/morph.cpp | 2 +- src/jit/namedintrinsiclist.h | 9 +- .../Collections/Generic/ComparerHelpers.cs | 3 +- .../Collections/Generic/EqualityComparer.cs | 16 +- src/vm/compile.cpp | 1 + src/vm/jitinterface.cpp | 117 ++++++++++ src/vm/jitinterface.h | 8 + src/vm/mscorlib.h | 11 + src/zap/zapinfo.cpp | 6 + src/zap/zapinfo.h | 4 + .../DefaultEqualityComparerPerf.cs | 207 ++++++++++++++++++ .../DefaultEqualityComparerPerf.csproj | 40 ++++ 28 files changed, 748 insertions(+), 63 deletions(-) create mode 100644 tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.cs create mode 100644 tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.csproj diff --git a/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h b/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h index 4ce775d177eb..29baaa8a3927 100644 --- a/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h +++ b/src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h @@ -119,6 +119,15 @@ CORINFO_METHOD_HANDLE resolveVirtualMethod(CORINFO_METHOD_HANDLE virtualMethod, CORINFO_CLASS_HANDLE implementingClass, CORINFO_CONTEXT_HANDLE ownerType); +// Given T, return the type of the default EqualityComparer. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE elemType); + +// Given resolved token that corresponds to an intrinsic classified as +// a CORINFO_INTRINSIC_GetRawHandle intrinsic, fetch the handle associated +// with the token. If this is not possible at compile-time (because the current method's +// code is shared and the token contains generic parameters) then indicate +// how the handle should be looked up at runtime. void expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult); @@ -126,8 +135,7 @@ void expandRawHandleIntrinsic( // If a method's attributes have (getMethodAttribs) CORINFO_FLG_INTRINSIC set, // getIntrinsicID() returns the intrinsic ID. // *pMustExpand tells whether or not JIT must expand the intrinsic. -CorInfoIntrinsics getIntrinsicID(CORINFO_METHOD_HANDLE method, bool* pMustExpand = NULL /* OUT */ - ); +CorInfoIntrinsics getIntrinsicID(CORINFO_METHOD_HANDLE method, bool* pMustExpand = NULL /* OUT */); // Is the given module the System.Numerics.Vectors module? // This defaults to false. diff --git a/src/ToolBox/superpmi/superpmi-shared/lwmlist.h b/src/ToolBox/superpmi/superpmi-shared/lwmlist.h index 898641c790c9..21f7906510ac 100644 --- a/src/ToolBox/superpmi/superpmi-shared/lwmlist.h +++ b/src/ToolBox/superpmi/superpmi-shared/lwmlist.h @@ -69,6 +69,7 @@ LWM(GetClassName, DWORDLONG, DWORD) LWM(GetClassNumInstanceFields, DWORDLONG, DWORD) LWM(GetClassSize, DWORDLONG, DWORD) LWM(GetCookieForPInvokeCalliSig, GetCookieForPInvokeCalliSigValue, DLDL) +LWM(GetDefaultEqualityComparerClass, DWORDLONG, DWORDLONG) LWM(GetDelegateCtor, Agnostic_GetDelegateCtorIn, Agnostic_GetDelegateCtorOut) LWM(GetEEInfo, DWORD, Agnostic_CORINFO_EE_INFO) LWM(GetEHinfo, DLD, Agnostic_CORINFO_EH_CLAUSE) diff --git a/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp b/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp index 02a940d2b5d7..0bf288d1969b 100644 --- a/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp @@ -3012,6 +3012,23 @@ CORINFO_METHOD_HANDLE MethodContext::repResolveVirtualMethod(CORINFO_METHOD_HAND return (CORINFO_METHOD_HANDLE)result; } +void MethodContext::recGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result) +{ + if (GetDefaultEqualityComparerClass == nullptr) + GetDefaultEqualityComparerClass = new LightWeightMap(); + + GetDefaultEqualityComparerClass->Add((DWORDLONG)cls, (DWORDLONG)result); +} +void MethodContext::dmpGetDefaultEqualityComparerClass(DWORDLONG key, DWORDLONG value) +{ + printf("GetDefaultEqualityComparerClass key cls-%016llX, value cls-%016llX", key, value); +} +CORINFO_CLASS_HANDLE MethodContext::repGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + CORINFO_CLASS_HANDLE result = (CORINFO_CLASS_HANDLE)GetDefaultEqualityComparerClass->Get((DWORDLONG)cls); + return result; +} + void MethodContext::recGetTokenTypeAsHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CLASS_HANDLE result) { if (GetTokenTypeAsHandle == nullptr) diff --git a/src/ToolBox/superpmi/superpmi-shared/methodcontext.h b/src/ToolBox/superpmi/superpmi-shared/methodcontext.h index 7f79b2b9f674..52515733d33c 100644 --- a/src/ToolBox/superpmi/superpmi-shared/methodcontext.h +++ b/src/ToolBox/superpmi/superpmi-shared/methodcontext.h @@ -872,6 +872,11 @@ class MethodContext CORINFO_CLASS_HANDLE implClass, CORINFO_CONTEXT_HANDLE ownerType); + void recGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result); + void dmpGetDefaultEqualityComparerClass(DWORDLONG key, DWORDLONG value); + CORINFO_CLASS_HANDLE repGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls); + + void recGetTokenTypeAsHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CLASS_HANDLE result); void dmpGetTokenTypeAsHandle(const GetTokenTypeAsHandleValue& key, DWORDLONG value); CORINFO_CLASS_HANDLE repGetTokenTypeAsHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken); @@ -1252,7 +1257,7 @@ class MethodContext }; // ********************* Please keep this up-to-date to ease adding more *************** -// Highest packet number: 161 +// Highest packet number: 162 // ************************************************************************************* enum mcPackets { @@ -1319,6 +1324,7 @@ enum mcPackets Packet_GetIntConfigValue = 151, // Added 2/12/2015 Packet_GetStringConfigValue = 152, // Added 2/12/2015 Packet_GetCookieForPInvokeCalliSig = 48, + Packet_GetDefaultEqualityComparerClass = 162, // Added 9/24/2017 Packet_GetDelegateCtor = 49, Packet_GetEEInfo = 50, Packet_GetEHinfo = 51, diff --git a/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 2a477a07e365..6051de8840e3 100644 --- a/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -236,6 +236,16 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HAND return result; } +// Given T, return the type of the default EqualityComparer. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + mc->cr->AddCall("getDefaultEqualityComparerClass"); + CORINFO_CLASS_HANDLE result = original_ICorJitInfo->getDefaultEqualityComparerClass(cls); + mc->recGetDefaultEqualityComparerClass(cls, result); + return result; +} + void interceptor_ICJI::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp index a54ead566118..17da100dac50 100644 --- a/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp @@ -163,6 +163,14 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HAND return original_ICorJitInfo->resolveVirtualMethod(virtualMethod, implementingClass, ownerType); } +// Given T, return the type of the default EqualityComparer. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + mcs->AddCall("getDefaultEqualityComparerClass"); + return original_ICorJitInfo->getDefaultEqualityComparerClass(cls); +} + void interceptor_ICJI::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp index 7e0ded9f0fed..df4cfcfbcd1d 100644 --- a/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp @@ -150,6 +150,13 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HAND return original_ICorJitInfo->resolveVirtualMethod(virtualMethod, implementingClass, ownerType); } +// Given T, return the type of the default EqualityComparer. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + return original_ICorJitInfo->getDefaultEqualityComparerClass(cls); +} + void interceptor_ICJI::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp index 2e78113991d8..7c119b86dd37 100644 --- a/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp +++ b/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp @@ -185,6 +185,15 @@ CORINFO_METHOD_HANDLE MyICJI::resolveVirtualMethod(CORINFO_METHOD_HANDLE virtua return result; } +// Given T, return the type of the default EqualityComparer. +// Returns null if the type can't be determined exactly. +CORINFO_CLASS_HANDLE MyICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls) +{ + jitInstance->mc->cr->AddCall("getDefaultEqualityComparerClass"); + CORINFO_CLASS_HANDLE result = jitInstance->mc->repGetDefaultEqualityComparerClass(cls); + return result; +} + void MyICJI::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/inc/corinfo.h b/src/inc/corinfo.h index 3171d6b00d33..aa024e054729 100644 --- a/src/inc/corinfo.h +++ b/src/inc/corinfo.h @@ -213,11 +213,11 @@ TODO: Talk about initializing strutures before use #define SELECTANY extern __declspec(selectany) #endif -SELECTANY const GUID JITEEVersionIdentifier = { /* 7f70c266-eada-427b-be8a-be1260e34b1b */ - 0x7f70c266, - 0xeada, - 0x427b, - {0xbe, 0x8a, 0xbe, 0x12, 0x60, 0xe3, 0x4b, 0x1b} +SELECTANY const GUID JITEEVersionIdentifier = { /* 76a743cd-8a07-471e-9ac4-cd5806a8ffac */ + 0x76a743cd, + 0x8a07, + 0x471e, + {0x9a, 0xc4, 0xcd, 0x58, 0x06, 0xa8, 0xff, 0xac} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2085,6 +2085,12 @@ class ICorStaticInfo CORINFO_CONTEXT_HANDLE ownerType = NULL /* IN */ ) = 0; + // Given T, return the type of the default EqualityComparer. + // Returns null if the type can't be determined exactly. + virtual CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType + ) = 0; + // Given resolved token that corresponds to an intrinsic classified as // a CORINFO_INTRINSIC_GetRawHandle intrinsic, fetch the handle associated // with the token. If this is not possible at compile-time (because the current method's diff --git a/src/jit/ICorJitInfo_API_names.h b/src/jit/ICorJitInfo_API_names.h index 34a8bc0626f2..fe86ea2e0687 100644 --- a/src/jit/ICorJitInfo_API_names.h +++ b/src/jit/ICorJitInfo_API_names.h @@ -168,5 +168,6 @@ DEF_CLR_API(getModuleNativeEntryPointRange) DEF_CLR_API(getExpectedTargetArchitecture) DEF_CLR_API(resolveVirtualMethod) DEF_CLR_API(expandRawHandleIntrinsic) +DEF_CLR_API(getDefaultEqualityComparerClass) #undef DEF_CLR_API diff --git a/src/jit/ICorJitInfo_API_wrapper.hpp b/src/jit/ICorJitInfo_API_wrapper.hpp index 78177e16d7f3..69311a5917ac 100644 --- a/src/jit/ICorJitInfo_API_wrapper.hpp +++ b/src/jit/ICorJitInfo_API_wrapper.hpp @@ -1620,6 +1620,16 @@ CORINFO_METHOD_HANDLE WrapICorJitInfo::resolveVirtualMethod( return result; } +CORINFO_CLASS_HANDLE WrapICorJitInfo::getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType) +{ + API_ENTER(getDefaultEqualityComparerClass); + CORINFO_CLASS_HANDLE result = wrapHnd->getDefaultEqualityComparerClass(elemType); + API_LEAVE(getDefaultEqualityComparerClass); + return result; +} + + void WrapICorJitInfo::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 1dcc76338c8c..4e322e0e4a2c 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -2966,12 +2966,13 @@ class Compiler IL_OFFSET rawILOffset); void impDevirtualizeCall(GenTreeCall* call, - GenTreePtr thisObj, CORINFO_METHOD_HANDLE* method, unsigned* methodFlags, CORINFO_CONTEXT_HANDLE* contextHandle, CORINFO_CONTEXT_HANDLE* exactContextHandle); + CORINFO_CLASS_HANDLE impGetSpecialIntrinsicExactReturnType(CORINFO_METHOD_HANDLE specialIntrinsicHandle); + bool impMethodInfo_hasRetBuffArg(CORINFO_METHOD_INFO* methInfo); GenTreePtr impFixupCallStructReturn(GenTreeCall* call, CORINFO_CLASS_HANDLE retClsHnd); diff --git a/src/jit/flowgraph.cpp b/src/jit/flowgraph.cpp index ff3269322b6e..355a0c5610b4 100644 --- a/src/jit/flowgraph.cpp +++ b/src/jit/flowgraph.cpp @@ -7056,6 +7056,19 @@ GenTreeCall* Compiler::fgGetStaticsCCtorHelper(CORINFO_CLASS_HANDLE cls, CorInfo GenTreeCall* result = gtNewHelperCallNode(helper, type, argList); result->gtFlags |= callFlags; + // If we're importing the special EqualityComparer.Default + // intrinsic, flag the helper call. Later during inlining, we can + // remove the helper call if the associated field lookup is unused. + if ((info.compFlags & CORINFO_FLG_JIT_INTRINSIC) != 0) + { + NamedIntrinsic ni = lookupNamedIntrinsic(info.compMethodHnd); + if (ni == NI_System_Collections_Generic_EqualityComparer_get_Default) + { + JITDUMP("\nmarking helper call [06%u] as special dce...\n", result->gtTreeID); + result->gtCallMoreFlags |= GTF_CALL_M_HELPER_SPECIAL_DCE; + } + } + return result; } @@ -22233,7 +22246,7 @@ Compiler::fgWalkResult Compiler::fgUpdateInlineReturnExpressionPlaceHolder(GenTr CORINFO_METHOD_HANDLE method = call->gtCallMethHnd; unsigned methodFlags = 0; CORINFO_CONTEXT_HANDLE context = nullptr; - comp->impDevirtualizeCall(call, tree, &method, &methodFlags, &context, nullptr); + comp->impDevirtualizeCall(call, &method, &methodFlags, &context, nullptr); } } } @@ -22928,8 +22941,8 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) GenTreeStmt* callStmt = inlineInfo->iciStmt; IL_OFFSETX callILOffset = callStmt->gtStmtILoffsx; GenTreeStmt* postStmt = callStmt->gtNextStmt; - GenTreePtr afterStmt = callStmt; // afterStmt is the place where the new statements should be inserted after. - GenTreePtr newStmt = nullptr; + GenTree* afterStmt = callStmt; // afterStmt is the place where the new statements should be inserted after. + GenTree* newStmt = nullptr; GenTreeCall* call = inlineInfo->iciCall->AsCall(); noway_assert(call->gtOper == GT_CALL); @@ -23074,6 +23087,8 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) if (argInfo.argHasSideEff) { noway_assert(argInfo.argIsUsed == false); + newStmt = nullptr; + bool append = true; if (argNode->gtOper == GT_OBJ || argNode->gtOper == GT_MKREFANY) { @@ -23084,16 +23099,92 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) } else { - newStmt = gtNewStmt(gtUnusedValNode(argNode), callILOffset); + // In some special cases, unused args with side effects can + // trigger further changes. + // + // (1) If the arg is a static field access and the field access + // was produced by a call to EqualityComparer.get_Default, the + // helper call to ensure the field has a value can be suppressed. + // This helper call is marked as a "Special DCE" helper during + // importation, over in fgGetStaticsCCtorHelper. + // + // (2) NYI. If, after tunneling through GT_RET_VALs, we find that + // the actual arg expression has no side effects, we can skip + // appending all together. This will help jit TP a bit. + // + // Chase through any GT_RET_EXPRs to find the actual argument + // expression. + GenTree* actualArgNode = argNode; + while (actualArgNode->gtOper == GT_RET_EXPR) + { + actualArgNode = actualArgNode->gtRetExpr.gtInlineCandidate; + } + + // For case (1) + // + // Look for the following tree shapes + // prejit: (IND (ADD (CONST, CALL(special dce helper...)))) + // jit : (COMMA (CALL(special dce helper...), (FIELD ...))) + if (actualArgNode->gtOper == GT_COMMA) + { + // Look for (COMMA (CALL(special dce helper...), (FIELD ...))) + GenTree* op1 = actualArgNode->gtOp.gtOp1; + GenTree* op2 = actualArgNode->gtOp.gtOp2; + if (op1->IsCall() && ((op1->gtCall.gtCallMoreFlags & GTF_CALL_M_HELPER_SPECIAL_DCE) != 0) && + (op2->gtOper == GT_FIELD) && ((op2->gtFlags & GTF_EXCEPT) == 0)) + { + JITDUMP("\nPerforming special dce on unused arg [%06u]:" + " actual arg [%06u] helper call [%06u]\n", + argNode->gtTreeID, actualArgNode->gtTreeID, op1->gtTreeID); + // Drop the whole tree + append = false; + } + } + else if (actualArgNode->gtOper == GT_IND) + { + // Look for (IND (ADD (CONST, CALL(special dce helper...)))) + GenTree* addr = actualArgNode->gtOp.gtOp1; + + if (addr->gtOper == GT_ADD) + { + GenTree* op1 = addr->gtOp.gtOp1; + GenTree* op2 = addr->gtOp.gtOp2; + if (op1->IsCall() && + ((op1->gtCall.gtCallMoreFlags & GTF_CALL_M_HELPER_SPECIAL_DCE) != 0) && + op2->IsCnsIntOrI()) + { + // Drop the whole tree + JITDUMP("\nPerforming special dce on unused arg [%06u]:" + " actual arg [%06u] helper call [%06u]\n", + argNode->gtTreeID, actualArgNode->gtTreeID, op1->gtTreeID); + append = false; + } + } + } } - afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt); -#ifdef DEBUG - if (verbose) + if (!append) { - gtDispTree(afterStmt); + assert(newStmt == nullptr); + JITDUMP("Arg tree side effects were discardable, not appending anything for arg\n"); } + else + { + // If we don't have something custom to append, + // just append the arg node as an unused value. + if (newStmt == nullptr) + { + newStmt = gtNewStmt(gtUnusedValNode(argNode), callILOffset); + } + + afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt); +#ifdef DEBUG + if (verbose) + { + gtDispTree(afterStmt); + } #endif // DEBUG + } } else if (argNode->IsBoxedValue()) { diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index 965b5243ee7a..7e06cdbd4f6b 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -6415,6 +6415,7 @@ GenTreeLclFld* Compiler::gtNewLclFldNode(unsigned lnum, var_types type, unsigned } GenTreePtr Compiler::gtNewInlineCandidateReturnExpr(GenTreePtr inlineCandidate, var_types type) + { assert(GenTree::s_gtNodeSizes[GT_RET_EXPR] == TREE_NODE_SZ_LARGE); diff --git a/src/jit/gentree.h b/src/jit/gentree.h index 62bd8cbb08a1..4361d16af3c8 100644 --- a/src/jit/gentree.h +++ b/src/jit/gentree.h @@ -3654,6 +3654,8 @@ struct GenTreeCall final : public GenTree // to restore real function address and load hidden argument // as the first argument for calli. It is CoreRT replacement for instantiating // stubs, because executable code cannot be generated at runtime. +#define GTF_CALL_M_HELPER_SPECIAL_DCE 0x00020000 // GT_CALL -- this helper call can be removed if it is part of a comma and + // the comma result is unused. // clang-format on @@ -4997,7 +4999,7 @@ struct GenTreeStoreInd : public GenTreeIndir struct GenTreeRetExpr : public GenTree { - GenTreePtr gtInlineCandidate; + GenTree* gtInlineCandidate; CORINFO_CLASS_HANDLE gtRetClsHnd; diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index d41cafcd17a5..fcf3ddc1efb8 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -3738,7 +3738,7 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis, switch (ni) { - case NI_Enum_HasFlag: + case NI_System_Enum_HasFlag: { GenTree* thisOp = impStackTop(1).val; GenTree* flagOp = impStackTop(0).val; @@ -3772,6 +3772,13 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis, break; } + case NI_System_Collections_Generic_EqualityComparer_get_Default: + { + // Flag for later handling during devirtualization. + isSpecial = true; + break; + } + default: break; } @@ -3923,25 +3930,31 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) const char* namespaceName = nullptr; const char* methodName = info.compCompHnd->getMethodNameFromMetadata(method, &className, &namespaceName); - // Currently we only have intrinsics at the method level, so we can check that - // namespaceName, className, and methodName are all not null upfront. + if ((namespaceName == nullptr) || (className == nullptr) || (methodName == nullptr)) + { + return result; + } - if ((namespaceName != nullptr) && (className != nullptr) && (methodName != nullptr)) + if (strcmp(namespaceName, "System") == 0) { - if (strcmp(namespaceName, "System") == 0) + if ((strcmp(className, "Enum") == 0) && (strcmp(methodName, "HasFlag") == 0)) { - if ((strcmp(className, "Enum") == 0) && (strcmp(methodName, "HasFlag") == 0)) - { - result = NI_Enum_HasFlag; - } - else if ((strcmp(className, "MathF") == 0) && (strcmp(methodName, "Round") == 0)) - { - result = NI_MathF_Round; - } - else if ((strcmp(className, "Math") == 0) && (strcmp(methodName, "Round") == 0)) - { - result = NI_Math_Round; - } + result = NI_System_Enum_HasFlag; + } + else if ((strcmp(className, "MathF") == 0) && (strcmp(methodName, "Round") == 0)) + { + result = NI_MathF_Round; + } + else if ((strcmp(className, "Math") == 0) && (strcmp(methodName, "Round") == 0)) + { + result = NI_Math_Round; + } + } + else if (strcmp(namespaceName, "System.Collections.Generic") == 0) + { + if ((strcmp(className, "EqualityComparer`1") == 0) && (strcmp(methodName, "get_Default") == 0)) + { + result = NI_System_Collections_Generic_EqualityComparer_get_Default; } } @@ -7742,29 +7755,25 @@ var_types Compiler::impImportCall(OPCODE opcode, } } - /* Is this a virtual or interface call? */ + // Store the "this" value in the call + call->gtFlags |= obj->gtFlags & GTF_GLOB_EFFECT; + call->gtCall.gtCallObjp = obj; + // Is this a virtual or interface call? if ((call->gtFlags & GTF_CALL_VIRT_KIND_MASK) != GTF_CALL_NONVIRT) { - /* only true object pointers can be virtual */ + // only true object pointers can be virtual assert(obj->gtType == TYP_REF); // See if we can devirtualize. - impDevirtualizeCall(call->AsCall(), obj, &callInfo->hMethod, &callInfo->methodFlags, - &callInfo->contextHandle, &exactContextHnd); + impDevirtualizeCall(call->AsCall(), &callInfo->hMethod, &callInfo->methodFlags, &callInfo->contextHandle, + &exactContextHnd); } - else + + if (impIsThis(obj)) { - if (impIsThis(obj)) - { - call->gtCall.gtCallMoreFlags |= GTF_CALL_M_NONVIRT_SAME_THIS; - } + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_NONVIRT_SAME_THIS; } - - /* Store the "this" value in the call */ - - call->gtFlags |= obj->gtFlags & GTF_GLOB_EFFECT; - call->gtCall.gtCallObjp = obj; } //------------------------------------------------------------------------- @@ -17686,7 +17695,7 @@ void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo, inlCurArgInfo->argIsInvariant = true; if (inlCurArgInfo->argIsThis && (curArgVal->gtOper == GT_CNS_INT) && (curArgVal->gtIntCon.gtIconVal == 0)) { - /* Abort, but do not mark as not inlinable */ + // Abort inlining at this call site inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_HAS_NULL_THIS); return; } @@ -18831,9 +18840,8 @@ bool Compiler::IsMathIntrinsic(GenTreePtr tree) // // Arguments: // call -- the call node to examine/modify -// thisObj -- the value of 'this' for the call // method -- [IN/OUT] the method handle for call. Updated iff call devirtualized. -// methodAttribs -- [IN/OUT] flags for the method to call. Updated iff call devirtualized. +// methodFlags -- [IN/OUT] flags for the method to call. Updated iff call devirtualized. // contextHandle -- [IN/OUT] context handle for the call. Updated iff call devirtualized. // exactContextHnd -- [OUT] updated context handle iff call devirtualized // @@ -18855,7 +18863,6 @@ bool Compiler::IsMathIntrinsic(GenTreePtr tree) // the 'this obj' of a subsequent virtual call. // void Compiler::impDevirtualizeCall(GenTreeCall* call, - GenTreePtr thisObj, CORINFO_METHOD_HANDLE* method, unsigned* methodFlags, CORINFO_CONTEXT_HANDLE* contextHandle, @@ -18940,9 +18947,41 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, } // See what we know about the type of 'this' in the call. - bool isExact = false; - bool objIsNonNull = false; - CORINFO_CLASS_HANDLE objClass = gtGetClassHandle(thisObj, &isExact, &objIsNonNull); + GenTree* thisObj = call->gtCallObjp->gtEffectiveVal(false); + GenTree* actualThisObj = nullptr; + bool isExact = false; + bool objIsNonNull = false; + CORINFO_CLASS_HANDLE objClass = gtGetClassHandle(thisObj, &isExact, &objIsNonNull); + + // See if we have special knowlege that can get us a type or a better type. + if ((objClass == nullptr) || !isExact) + { + actualThisObj = thisObj; + + // Walk back through any return expression placeholders + while (actualThisObj->OperGet() == GT_RET_EXPR) + { + actualThisObj = actualThisObj->gtRetExpr.gtInlineCandidate; + } + + // See if we landed on a call to a special intrinsic method + if (actualThisObj->IsCall()) + { + GenTreeCall* thisObjCall = actualThisObj->AsCall(); + if ((thisObjCall->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC) != 0) + { + assert(thisObjCall->gtCallType == CT_USER_FUNC); + CORINFO_METHOD_HANDLE specialIntrinsicHandle = thisObjCall->gtCallMethHnd; + CORINFO_CLASS_HANDLE specialObjClass = impGetSpecialIntrinsicExactReturnType(specialIntrinsicHandle); + if (specialObjClass != nullptr) + { + objClass = specialObjClass; + isExact = true; + objIsNonNull = true; + } + } + } + } // Bail if we know nothing. if (objClass == nullptr) @@ -19164,6 +19203,64 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, #endif // defined(DEBUG) } +//------------------------------------------------------------------------ +// impGetSpecialIntrinsicExactReturnType: Look for special cases where a call +// to an intrinsic returns an exact type +// +// Arguments: +// methodHnd -- handle for the special intrinsic method +// +// Returns: +// Exact class handle returned by the intrinsic call, if known. +// Nullptr if not known, or not likely to lead to beneficial optimization. + +CORINFO_CLASS_HANDLE Compiler::impGetSpecialIntrinsicExactReturnType(CORINFO_METHOD_HANDLE methodHnd) +{ + JITDUMP("Special intrinsic: looking for exact type returned by %s\n", eeGetMethodFullName(methodHnd)); + + CORINFO_CLASS_HANDLE result = nullptr; + + // See what intrinisc we have... + const NamedIntrinsic ni = lookupNamedIntrinsic(methodHnd); + switch (ni) + { + case NI_System_Collections_Generic_EqualityComparer_get_Default: + { + // Expect one class generic parameter; figure out which it is. + CORINFO_SIG_INFO sig; + info.compCompHnd->getMethodSig(methodHnd, &sig); + assert(sig.sigInst.classInstCount == 1); + CORINFO_CLASS_HANDLE typeHnd = sig.sigInst.classInst[0]; + assert(typeHnd != nullptr); + const DWORD typeAttribs = info.compCompHnd->getClassAttribs(typeHnd); + const bool isFinalType = ((typeAttribs & CORINFO_FLG_FINAL) != 0); + + // If we do not have a final type, devirt & inlining is + // unlikely to result in much simplification. + if (isFinalType) + { + result = info.compCompHnd->getDefaultEqualityComparerClass(typeHnd); + JITDUMP("Special intrinsic for type %s: return type is %s\n", eeGetClassName(typeHnd), + result != nullptr ? eeGetClassName(result) : "unknown"); + } + else + { + JITDUMP("Special intrinsic for type %s: type not final, so deferring opt\n", eeGetClassName(typeHnd)); + } + + break; + } + + default: + { + JITDUMP("This special intrinsic not handled, sorry...\n"); + break; + } + } + + return result; +} + //------------------------------------------------------------------------ // impAllocateToken: create CORINFO_RESOLVED_TOKEN into jit-allocated memory and init it. // diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 471fe8a663c9..034b6d653e52 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -8865,7 +8865,7 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) // Check for a new-style jit intrinsic. const NamedIntrinsic ni = lookupNamedIntrinsic(call->gtCallMethHnd); - if (ni == NI_Enum_HasFlag) + if (ni == NI_System_Enum_HasFlag) { GenTree* thisOp = call->gtCallObjp; GenTree* flagOp = call->gtCallArgs->gtOp.gtOp1; diff --git a/src/jit/namedintrinsiclist.h b/src/jit/namedintrinsiclist.h index 2cbdb4596546..fa75cc27b6f8 100644 --- a/src/jit/namedintrinsiclist.h +++ b/src/jit/namedintrinsiclist.h @@ -9,10 +9,11 @@ enum NamedIntrinsic { - NI_Illegal = 0, - NI_Enum_HasFlag = 1, - NI_MathF_Round = 2, - NI_Math_Round = 3 + NI_Illegal = 0, + NI_System_Enum_HasFlag = 1, + NI_MathF_Round = 2, + NI_Math_Round = 3, + NI_System_Collections_Generic_EqualityComparer_get_Default = 4 }; #endif // _NAMEDINTRINSICLIST_H_ diff --git a/src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs b/src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs index 3e428413d4df..1c4b9bbc0985 100644 --- a/src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs +++ b/src/mscorlib/src/System/Collections/Generic/ComparerHelpers.cs @@ -23,7 +23,8 @@ internal static class ComparerHelpers /// /// The type to create the default comparer for. /// - /// The logic in this method is replicated in vm/compile.cpp to ensure that NGen saves the right instantiations. + /// The logic in this method is replicated in vm/compile.cpp to ensure that NGen saves the right instantiations, + /// and in vm/jitinterface.cpp so the jit can model the behavior of this method. /// internal static object CreateDefaultComparer(Type type) { diff --git a/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs b/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs index 2b2869ab4a6c..06af626526f3 100644 --- a/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs +++ b/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs @@ -21,7 +21,7 @@ public abstract class EqualityComparer : IEqualityComparer, IEqualityComparer { // To minimize generic instantiation overhead of creating the comparer per type, we keep the generic portion of the code as small // as possible and define most of the creation logic in a non-generic class. - public static EqualityComparer Default { get; } = (EqualityComparer)ComparerHelpers.CreateDefaultEqualityComparer(typeof(T)); + public static EqualityComparer Default { [Intrinsic] get; } = (EqualityComparer)ComparerHelpers.CreateDefaultEqualityComparer(typeof(T)); public abstract bool Equals(T x, T y); public abstract int GetHashCode(T obj); @@ -70,6 +70,7 @@ bool IEqualityComparer.Equals(object x, object y) [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal class GenericEqualityComparer : EqualityComparer where T : IEquatable { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T x, T y) { if (x != null) @@ -81,6 +82,7 @@ public override bool Equals(T x, T y) return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) => obj?.GetHashCode() ?? 0; internal override int IndexOf(T[] array, T value, int startIndex, int count) @@ -137,6 +139,7 @@ public override int GetHashCode() => [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal sealed class NullableEqualityComparer : EqualityComparer where T : struct, IEquatable { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T? x, T? y) { if (x.HasValue) @@ -148,6 +151,7 @@ public override bool Equals(T? x, T? y) return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T? obj) => obj.GetHashCode(); internal override int IndexOf(T?[] array, T? value, int startIndex, int count) @@ -202,6 +206,7 @@ public override int GetHashCode() => [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal sealed class ObjectEqualityComparer : EqualityComparer { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T x, T y) { if (x != null) @@ -213,6 +218,7 @@ public override bool Equals(T x, T y) return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) => obj?.GetHashCode() ?? 0; internal override int IndexOf(T[] array, T value, int startIndex, int count) @@ -297,11 +303,13 @@ public override int GetHashCode(string obj) [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal sealed class ByteEqualityComparer : EqualityComparer { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(byte x, byte y) { return x == y; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(byte b) { return b.GetHashCode(); @@ -346,6 +354,7 @@ public override int GetHashCode() => [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal class EnumEqualityComparer : EqualityComparer, ISerializable where T : struct { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T x, T y) { int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(x); @@ -353,6 +362,7 @@ public override bool Equals(T x, T y) return x_final == y_final; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) { int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); @@ -413,6 +423,7 @@ public SByteEnumEqualityComparer() { } // This is used by the serialization engine. public SByteEnumEqualityComparer(SerializationInfo information, StreamingContext context) { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) { int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); @@ -429,6 +440,7 @@ public ShortEnumEqualityComparer() { } // This is used by the serialization engine. public ShortEnumEqualityComparer(SerializationInfo information, StreamingContext context) { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) { int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); @@ -440,6 +452,7 @@ public override int GetHashCode(T obj) [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] internal sealed class LongEnumEqualityComparer : EqualityComparer, ISerializable where T : struct { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(T x, T y) { long x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong(x); @@ -447,6 +460,7 @@ public override bool Equals(T x, T y) return x_final == y_final; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode(T obj) { long x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong(obj); diff --git a/src/vm/compile.cpp b/src/vm/compile.cpp index b3d187c555ff..196e2163a5f5 100644 --- a/src/vm/compile.cpp +++ b/src/vm/compile.cpp @@ -5162,6 +5162,7 @@ static void SpecializeComparer(SString& ss, Instantiation& inst) // // This method has duplicated logic from bcl\system\collections\generic\equalitycomparer.cs +// and matching logic in jitinterface.cpp // static void SpecializeEqualityComparer(SString& ss, Instantiation& inst) { diff --git a/src/vm/jitinterface.cpp b/src/vm/jitinterface.cpp index 83e19be8a12e..aa4106419845 100644 --- a/src/vm/jitinterface.cpp +++ b/src/vm/jitinterface.cpp @@ -8683,6 +8683,123 @@ void CEEInfo::expandRawHandleIntrinsic( UNREACHABLE(); // only called with CoreRT. } +/*********************************************************************/ +CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE elemType) +{ + CONTRACTL { + SO_TOLERANT; + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + CORINFO_CLASS_HANDLE result = NULL; + + JIT_TO_EE_TRANSITION(); + + result = getDefaultEqualityComparerClassHelper(elemType); + + EE_TO_JIT_TRANSITION(); + + return result; +} + +CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClassHelper(CORINFO_CLASS_HANDLE elemType) +{ + CONTRACTL { + SO_TOLERANT; + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + // Mirrors the logic in BCL's CompareHelpers.CreateDefaultEqualityComparer + // And in compile.cpp's SpecializeEqualityComparer + TypeHandle elemTypeHnd(elemType); + + // Special case for byte + if (elemTypeHnd.AsMethodTable()->HasSameTypeDefAs(MscorlibBinder::GetClass(CLASS__ELEMENT_TYPE_U1))) + { + return CORINFO_CLASS_HANDLE(MscorlibBinder::GetClass(CLASS__BYTE_EQUALITYCOMPARER)); + } + + // Else we'll need to find the appropriate instantation + Instantiation inst(&elemTypeHnd, 1); + + // If T implements IEquatable + if (elemTypeHnd.CanCastTo(TypeHandle(MscorlibBinder::GetClass(CLASS__IEQUATABLEGENERIC)).Instantiate(inst))) + { + TypeHandle resultTh = ((TypeHandle)MscorlibBinder::GetClass(CLASS__GENERIC_EQUALITYCOMPARER)).Instantiate(inst); + return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); + } + + // Nullable + if (Nullable::IsNullableType(elemTypeHnd)) + { + Instantiation nullableInst = elemTypeHnd.AsMethodTable()->GetInstantiation(); + TypeHandle nullableComparer = TypeHandle(MscorlibBinder::GetClass(CLASS__IEQUATABLEGENERIC)).Instantiate(nullableInst); + if (nullableInst[0].CanCastTo(nullableComparer)) + { + return CORINFO_CLASS_HANDLE(nullableComparer.GetMethodTable()); + } + } + + // Enum + // + // We need to special case the Enum comparers based on their underlying type, + // to avoid boxing and call the correct versions of GetHashCode. + if (elemTypeHnd.IsEnum()) + { + MethodTable* targetClass = NULL; + CorElementType normType = elemTypeHnd.GetVerifierCorElementType(); + + switch(normType) + { + case ELEMENT_TYPE_I1: + { + targetClass = MscorlibBinder::GetClass(CLASS__SBYTE_ENUM_EQUALITYCOMPARER); + break; + } + + case ELEMENT_TYPE_I2: + { + targetClass = MscorlibBinder::GetClass(CLASS__SHORT_ENUM_EQUALITYCOMPARER); + break; + } + + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + { + targetClass = MscorlibBinder::GetClass(CLASS__ENUM_EQUALITYCOMPARER); + break; + } + + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + { + targetClass = MscorlibBinder::GetClass(CLASS__LONG_ENUM_EQUALITYCOMPARER); + break; + } + + default: + break; + } + + if (targetClass != NULL) + { + TypeHandle resultTh = ((TypeHandle)targetClass->GetCanonicalMethodTable()).Instantiate(inst); + return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); + } + } + + // Default case + TypeHandle resultTh = ((TypeHandle)MscorlibBinder::GetClass(CLASS__OBJECT_EQUALITYCOMPARER)).Instantiate(inst); + + return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); +} + /*********************************************************************/ void CEEInfo::getFunctionEntryPoint(CORINFO_METHOD_HANDLE ftnHnd, CORINFO_CONST_LOOKUP * pResult, diff --git a/src/vm/jitinterface.h b/src/vm/jitinterface.h index 8ba65c396421..b27e160e8043 100644 --- a/src/vm/jitinterface.h +++ b/src/vm/jitinterface.h @@ -743,6 +743,14 @@ class CEEInfo : public ICorJitInfo CORINFO_CONTEXT_HANDLE ownerType ); + CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType + ); + + CORINFO_CLASS_HANDLE getDefaultEqualityComparerClassHelper( + CORINFO_CLASS_HANDLE elemType + ); + void expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult); diff --git a/src/vm/mscorlib.h b/src/vm/mscorlib.h index b86930693d6a..4942e0d8a926 100644 --- a/src/vm/mscorlib.h +++ b/src/vm/mscorlib.h @@ -1449,6 +1449,17 @@ DEFINE_CLASS(UTF8BUFFERMARSHALER, StubHelpers, UTF8BufferMarshaler) DEFINE_METHOD(UTF8BUFFERMARSHALER, CONVERT_TO_NATIVE, ConvertToNative, NoSig) DEFINE_METHOD(UTF8BUFFERMARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, NoSig) +// Classes referenced in EqualityComparer.Default optimization + +DEFINE_CLASS(BYTE_EQUALITYCOMPARER, CollectionsGeneric, ByteEqualityComparer) +DEFINE_CLASS(SHORT_ENUM_EQUALITYCOMPARER, CollectionsGeneric, ShortEnumEqualityComparer`1) +DEFINE_CLASS(SBYTE_ENUM_EQUALITYCOMPARER, CollectionsGeneric, SByteEnumEqualityComparer`1) +DEFINE_CLASS(ENUM_EQUALITYCOMPARER, CollectionsGeneric, EnumEqualityComparer`1) +DEFINE_CLASS(LONG_ENUM_EQUALITYCOMPARER, CollectionsGeneric, LongEnumEqualityComparer`1) +DEFINE_CLASS(NULLABLE_EQUALITYCOMPARER, CollectionsGeneric, NullableEqualityComparer`1) +DEFINE_CLASS(GENERIC_EQUALITYCOMPARER, CollectionsGeneric, GenericEqualityComparer`1) +DEFINE_CLASS(OBJECT_EQUALITYCOMPARER, CollectionsGeneric, ObjectEqualityComparer`1) + #undef DEFINE_CLASS #undef DEFINE_METHOD #undef DEFINE_FIELD diff --git a/src/zap/zapinfo.cpp b/src/zap/zapinfo.cpp index b8ef3eab91a6..01d3a3b7cc9e 100644 --- a/src/zap/zapinfo.cpp +++ b/src/zap/zapinfo.cpp @@ -3704,6 +3704,12 @@ CORINFO_METHOD_HANDLE ZapInfo::resolveVirtualMethod( return m_pEEJitInfo->resolveVirtualMethod(virtualMethod, implementingClass, ownerType); } +CORINFO_CLASS_HANDLE ZapInfo::getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType) +{ + return m_pEEJitInfo->getDefaultEqualityComparerClass(elemType); +} + void ZapInfo::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult) diff --git a/src/zap/zapinfo.h b/src/zap/zapinfo.h index d533975c798e..5bb86dd88aae 100644 --- a/src/zap/zapinfo.h +++ b/src/zap/zapinfo.h @@ -675,6 +675,10 @@ class ZapInfo CORINFO_CONTEXT_HANDLE ownerType ); + CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass( + CORINFO_CLASS_HANDLE elemType + ); + void expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_GENERICHANDLE_RESULT * pResult); diff --git a/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.cs b/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.cs new file mode 100644 index 000000000000..b9da9f17fa7f --- /dev/null +++ b/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.cs @@ -0,0 +1,207 @@ +// 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 Microsoft.Xunit.Performance; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Xunit; + +[assembly: OptimizeForBenchmarks] + +// Performance tests for optimizations related to EqualityComparer.Default + +namespace Devirtualization +{ + public class EqualityComparerFixture where T : IEquatable + { + IEqualityComparer comparer; + + public EqualityComparerFixture(IEqualityComparer customComparer = null) + { + comparer = customComparer ?? EqualityComparer.Default; + } + + // Baseline method showing unoptimized performance + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public bool CompareNoOpt(ref T a, ref T b) + { + return EqualityComparer.Default.Equals(a, b); + } + + // The code this method invokes should be well-optimized + [MethodImpl(MethodImplOptions.NoInlining)] + public bool Compare(ref T a, ref T b) + { + return EqualityComparer.Default.Equals(a, b); + } + + // This models how Dictionary uses a comparer. We're not + // yet able to optimize such cases. + [MethodImpl(MethodImplOptions.NoInlining)] + public bool CompareCached(ref T a, ref T b) + { + return comparer.Equals(a, b); + } + + private static IEqualityComparer Wrapped() + { + return EqualityComparer.Default; + } + + // We would need enhancements to late devirtualization + // to optimize this case. + [MethodImpl(MethodImplOptions.NoInlining)] + public bool CompareWrapped(ref T x, ref T y) + { + return Wrapped().Equals(x, y); + } + + public bool BenchCompareNoOpt(ref T t, long count) + { + bool result = true; + for (int i = 0; i < count; i++) + { + result &= CompareNoOpt(ref t, ref t); + } + return result; + } + + public bool BenchCompare(ref T t, long count) + { + bool result = true; + for (int i = 0; i < count; i++) + { + result &= Compare(ref t, ref t); + } + return result; + } + + public bool BenchCompareCached(ref T t, long count) + { + bool result = true; + for (int i = 0; i < count; i++) + { + result &= CompareCached(ref t, ref t); + } + return result; + } + + public bool BenchCompareWrapped(ref T t, long count) + { + bool result = true; + for (int i = 0; i < count; i++) + { + result &= CompareWrapped(ref t, ref t); + } + return result; + } + } + + public class EqualityComparer + { + +#if DEBUG + public const int Iterations = 1; +#else + public const int Iterations = 150 * 1000 * 1000; +#endif + + public enum E + { + RED = 1, + BLUE = 2 + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Consume(bool b) { } + + [Benchmark(InnerIterationCount = Iterations)] + public static void ValueTupleCompareNoOpt() + { + var valueTupleFixture = new EqualityComparerFixture>(); + var v0 = new ValueTuple(3, E.RED, 11); + var result = true; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + result &= valueTupleFixture.BenchCompareNoOpt(ref v0, Benchmark.InnerIterationCount); + } + } + + Consume(result); + } + + [Benchmark(InnerIterationCount = Iterations)] + public static void ValueTupleCompare() + { + var valueTupleFixture = new EqualityComparerFixture>(); + var v0 = new ValueTuple(3, E.RED, 11); + var result = true; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + result &= valueTupleFixture.BenchCompare(ref v0, Benchmark.InnerIterationCount); + } + } + + Consume(result); + } + + [Benchmark(InnerIterationCount = Iterations)] + public static void ValueTupleCompareCached() + { + var valueTupleFixture = new EqualityComparerFixture>(); + var v0 = new ValueTuple(3, E.RED, 11); + var result = true; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + result &= valueTupleFixture.BenchCompareCached(ref v0, Benchmark.InnerIterationCount); + } + } + + Consume(result); + } + + [Benchmark(InnerIterationCount = Iterations)] + public static void ValueTupleCompareWrapped() + { + var valueTupleFixture = new EqualityComparerFixture>(); + var v0 = new ValueTuple(3, E.RED, 11); + var result = true; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + result &= valueTupleFixture.BenchCompareWrapped(ref v0, Benchmark.InnerIterationCount); + } + } + + Consume(result); + } + + public static int Main() + { + var valueTupleFixture = new EqualityComparerFixture>(); + var v0 = new ValueTuple(3, E.RED, 11); + + bool vtCompare = valueTupleFixture.Compare(ref v0, ref v0); + bool vtCompareNoOpt = valueTupleFixture.CompareNoOpt(ref v0, ref v0); + bool vtCompareCached = valueTupleFixture.CompareCached(ref v0, ref v0); + bool vtCompareWrapped = valueTupleFixture.CompareWrapped(ref v0, ref v0); + + bool vtOk = vtCompare & vtCompareNoOpt & vtCompareCached & vtCompareWrapped; + + return vtOk ? 100 : 0; + } + } +} diff --git a/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.csproj b/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.csproj new file mode 100644 index 000000000000..7f06379d37ef --- /dev/null +++ b/tests/src/JIT/Performance/CodeQuality/Devirtualization/DefaultEqualityComparerPerf.csproj @@ -0,0 +1,40 @@ + + + + + Debug + AnyCPU + 2.0 + {C1BFD48A-A83F-4767-8EB2-3E2C50906681} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + .NETStandard,Version=v1.4 + netstandard1.4 + + + + pdbonly + true + + + pdbonly + true + + + + False + + + + + + + + + + + + $(JitPackagesConfigFileDirectory)benchmark\obj\project.assets.json + +